Testing a function entirely using stubs

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);
        });
    });
  },
}
  1. Now when I write tests for this function, I have a case where I stub Promise.all() to throw error.
  2. For my second test, 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.
  3. 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.

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!

Answers:

Answer

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 Promises, 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.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.