Tian Qin
Tian Qin

Reputation: 183

unit testing ava + sinon for api call, one of the stub is not working

I have a test case need to test a function which has 2 upper layer promise functions. I stub all of these 3 functions, the first 2 works fine, but the last one seems like no working..

classD:

const obj1 = require('./classA');
const obj2 = require('./classB');
const obj3 = require('./classC');

const getTeam = () =>{
    return new Promise((resolve, reject) => {
        obj1.promiseFunc1().then(res1 => {
            if(res1.error){
              reject(res1); 
            }else{
              resolve({Teamid: 1, TeamName: 'goodName'});
            }
    })
}

const getMember = () => {
    return new Promise((resolve, reject) => {
      obj2.promiseFunc2().then(res2 => {
        if(res2.id.length === 0){
          obj3.promiseFunc3().then(res3 => {
            if(res3.error){
              reject(res3); 
            }else{
              getTeam().then(res => {
                 if(res.error){
                    reject(res);
                  }else{
                   resolve(res);
                  }
              });
            }
        })
      }
   })    
 })
}

Below is the test case:

Test.js

const obj1 = require('./classA');
const obj2 = require('./classB');
const obj3 = require('./classC');
const obj4 = require('./classD');
const sandbox = require('sinon').createSandbox();

test.serial('test getMember', async (t) => {
  sandbox.restore();
  sandbox.stub(obj2, 'promiseFunc2').resolves({id: [], error: false});
  sandbox.stub(obj3, 'promiseFunc3').resolves({status: 200, error: false});

  sandbox.stub(obj4, 'getTeam').resolves({data: [], error: false});

  const result = await obj4.getMember();
  console.log('result :' + JSON.stringify(result));
})

So I need to runa test case for the function obj4.getMember() which will call obj2.promiseFunc2, obj3.promiseFunc3, and obj4.getTeam(), they all return promises. I stub the promiseFunc2 and promiseFunc3 with no problem. But for some reason the obj4.getTeam() seems like not working well. I returned {Teamid: 1, TeamName: 'goodName'} which is the response from last test cases in the same file for getTeam()...

I expected to return {data: [], error: false} which is the outcome I stub for..

Anyone know why the stub is not working? How to make it work?

Thank you very much!

Upvotes: 0

Views: 465

Answers (1)

geoffrey
geoffrey

Reputation: 2454

Calling getTeam as a function from obj4.getMember could be bypassing Simon's stub.

Try the following:

const obj1 = require('./classA');
const obj2 = require('./classB');
const obj3 = require('./classC');

const obj4 = {};

obj4.getTeam = () =>{
    return new Promise((resolve, reject) => {
        obj1.promiseFunc1().then(res1 => {
            if(res1.error){
              reject(res1); 
            }else{
              resolve({Teamid: 1, TeamName: 'goodName'});
            }
        })
    })
}

obj4.getMember = () => {
    return new Promise((resolve, reject) => {
      obj2.promiseFunc2().then(res2 => {
        if(res2.id.length === 0){
          obj3.promiseFunc3().then(res3 => {
            if(res3.error){
              reject(res3); 
            }else{
              obj4.getTeam().then(res => {
                 if(res.error){
                    reject(res);
                  }else{
                   resolve(res);
                  }
              });
            }
        })
      }
   })    
 })
}
        
module.exports = obj4;

I don't think you should call modules "classes" as it sets wrong expectations.


As for Promise chaining, it is unfortunate your API does not reject on errors, which forces you to add a lot of error handling logic, but a helper could convert these promises to rejected promises

const rejectOn = predicate => f => (...xs) =>
    f(...xs).then(x => predicate(x) ? Promise.reject(x) : x);

const error = x => x.error;

With this in place, the following would be easier to read and functionally identical (assuming objN.promiseFuncN are not methods with a context). Note that there is no need to wrap a promise-returning function in a new Promise and that returning a Promise replaces the surrounding Promise, removing a lot of boilerplate from your code.


obj4.getTeam = () => 
    rejectOn(error)(obj1.promiseFunc1)()
    .then(() => ({ Teamid: 1, TeamName: 'goodName' }));


obj4.getMember = () =>
    rejectOn(x => x.id.length)(obj2.promiseFunc2)()
    .then(rejectOn(error)(obj3.promiseFunc3))
    .then(rejectOn(error)(obj4.getTeam));

Maybe you should have a talk with your manager. Clean code does not slow you down. Rotting code slows you down.

Upvotes: 1

Related Questions