I have been writing tests for the past couple of weeks now. At my place of work we are using Mocha as our test runner and Chai as an assertion library. I am also using Sinon for creating stubs and there is something that is bugging me constantly. I have written tests for a couple of functions where I stub every dependency in the function and worst I am not even considering the arguments which the function that I am testing is accepting. Let me give an example
module.exports = {
"someFunc": (arg1, arg2) => {
return new Promise((resolve, reject) => {
Promise.all(arg1).then(data => {
let someArray = ourHelperLib.toArray(data);
let someObj = ourHelperLib.toObject(arg2);
if(someArray.length == 0){
reject("error");
}else{
resolve({
"array": someArray,
"object": someObj
});
}
}).catch(err => {
reject(err);
});
});
},
}
Promise.all()
to throw error.Promise.all()
to return a false positive value and stub ourHelperLib.toArray()
to throw error and check if the function handles it or not.Promise.all()
, ourHelperLib.toArray()
and ourHelperLib.toObject()
to return false positives and then check the output for a resolved promise with a value that is the resultant of the operations.From the function definition it is clear that both the arguments passed to the function are passed directly to the dependencies that I am stubbing hence I can ignore those values completely, here's what I mean
const stubOurHelperLibToThrowError = argFromCaller => {
throw new Error("This is an error");
}
Since I am not handling the argument passed to my stub function, I am not at all testing the function on the basis of the data that is passed into it. I am simply testing the logic structure of the function someFunc()
.
Is this a good practice? I haven't found a lot of solid answers and since I am responsible for introducing guidelines for writing unit tests where I am working currently, this is something that I think is crucial.
Peace!
You can pass promises to your function without having to stub anything for a lot of what you are describing.
I have a case where I stub Promise.all() to throw error
Instead of stubbing Promise.all
, just pass an array with a rejected Promise
to your function:
someFunc([Promise.reject(new Error('fail'))], null)
...which will cause the Promise.all
to drop into the catch
and reject with the error.
I stub Promise.all() to return a false positive value and stub ourHelperLib.toArray() to throw error and check if the function handles it or not
Again, instead of stubbing Promise.all
, just pass an array with a resolved Promise
:
someFunc([Promise.resolve('a value')], null)
You can either stub ourHelperLib.toArray
to throw an error or have your Promise
array resolve to something you know will cause ourHelperLib.toArray
to throw.
For my third test I stub Promise.all(), ourHelperLib.toArray() and ourHelperLib.toObject() to return false positives and then check the output for a resolved promise with a value that is the resultant of the operations.
Stubbing ourHelperLib.toArray
and ourHelperLib.toObject
is optional. Unless they are computationally expensive (for example, if they make network calls) then it usually makes sense to call them like normal.
You can pass the data you want to give ourHelperLib.toArray
in the array of resolved Promise
s, and simply pass the value you want to send to ourHelperLib.toObject
as the second argument:
someFunc([
Promise.resolve('value 1 for ourHelperLib.toArray'),
Promise.resolve('value 2 for ourHelperLib.toArray')
], 'value for ourHelperLib.toObject')
...and check that the resulting Promise
resolves to the expected value.
In general it is best practice to adhere to black box testing.
This function doesn't appear to have any side-effects and simply returns a Promise
that resolves to a result based on the parameters passed.
Unless the function has computationally expensive dependencies, then whenever possible it is best to test a function like this by simply passing parameters and verifying the result.