Sinon to unit test 'catch' statement

10,536

Solution 1

With Mocha, Chai and Sinon it can be implemented with stubbed method getUser.

const User = require("./fetchUserModule");

describe('User module', () => {
    beforeEach(() => User.getUser = sinon.stub());

    afterEach(() => User.getUser.reset());

    it('returns user if `getUser` returns data', () => {
        const user = {name: 'John'};
        User.getUser.withArgs("abc123").returns(Promise.resolve(user));

        return User.fetchUser("abc123").then(result => {
            expect(result).to.equal(user)
        }).catch(error => {
            expect(error).to.be.undefined;
        })
    });

    it('throws error if `getUser` is rejected', () => {
        User.getUser.withArgs("abc123").returns(Promise.reject());

        return User.fetchUser("abc123").then(result => {
            expect(result).to.be.undefined;
        }).catch(err => {
            expect(err).to.eql({error: 'My Error'})
        })
    });
});

Solution 2

Start with anything in your "logic" that can throw an error.

If not you would need to stub this.getUser to reject or throw an error instead of returning data. sinon-as-promised patches sinon.stub to include the .resolves and .rejects promise helpers.

const sinon  = require('sinon')
require('sinon-as-promised')

Setup the stub for the failure tests.

before(function(){
  sinon.stub(user, 'getUser').rejects(new Error('whatever'))
})

after(function(){
  user.getUser.restore()
})

Then either catch the .fetchUser error or use chai-as-promised for some sugar.

it('test fetchUser', function() {
  return user.fetchUser('abc123')
    .then(()=> expect.fail('fetchUser should be rejected'))
    .catch(err => {
      expect(err.message).to.eql('whatever')
    })
})

it('test fetchUser', function() {
  return expect(user.fetchUser('abc123')).to.be.rejectedWith(Error)
})

or async if you live in the new world

it('test fetchUser', async function() {
  try { 
    await user.fetchUser('abc123')
    expect.fail('fetchUser should be rejected'))
  } catch(err) {
    expect(err.message).to.eql('whatever')
  }
})

As a side note, you don't need to wrap something that already returns a promise in new Promise and be careful about losing error information when chaining multiple .catch handlers.

fetchUser: function (myUserId) {
  return this.getUser(myUserId)
    .then(user => {
      //logic
      return user
    })
    .catch(err => {
      let error = new Error('My Error')
      error.original = err
      reject(error)
    });
}
Share:
10,536

Related videos on Youtube

sketchthat
Author by

sketchthat

Updated on June 04, 2022

Comments

  • sketchthat
    sketchthat almost 2 years

    I've got a simple function such as;

    module.exports = {
      fetchUser:function(myUserId) {
        return new Promise((resolve, reject) => {
          this.getUser(myUserId)
            .then(user => {
              // do logic // then return user
    
              return user;
            })
            .then(resolve)
            .catch(err => {
              // whoops there has been an error
    
              let error = { error: 'My Error' };
    
              reject(error);
            });
        });
      }
    };
    

    I want to unit test both the resolve and reject result.

    A simple chai test would be;

    var expect = require('chai').expect;
    var user = require('./user');
    
    describe('User module', function() {
      it('test fetchUser', function() {
        let _user = user.fetchUser('abc123');
    
        return _user
          .then(user => {
            expect(data).to.be.an('object');
          });
      });
    

    Using sinon or another library, how can I for the fetchUser function to throw that reject error?

  • sketchthat
    sketchthat over 7 years
    Thanks. If I setup the test this way it doesn't actually ever enter my catch codeblock does it? The stub just fakes the response to match the error thrown from my catch.
  • sketchthat
    sketchthat over 7 years
    Thanks Matt. I didn't realise that last point, I will update my codebase.
  • Krzysztof Safjanowski
    Krzysztof Safjanowski over 7 years
    Yes, it enters to catch statement as error is equal to {error: 'My Error'}. What you need more to proof that it works?
  • sketchthat
    sketchthat over 7 years
    Thanks, I was just checking, I don't fully understand stubs yet. I'll test it out.
  • Krzysztof Safjanowski
    Krzysztof Safjanowski over 7 years
    I tested it before post answer - it works as expected …