Search code examples
node.jssinonsinon-chai

Issue when testing function with sinon and chai


I am trying to start using testing on my projects, and some things I am able to test properly but others don't. Here is the function I want to test.

exports.rateReview = async (req, res, next) => {
    try {
        const review = await Review.findById(req.body.id);
        if (review) {
            if (req.body.type === 'like') await review.like();

            if (req.body.type === 'dislike') await review.dislike();
        }

        res.send('Ok');
    } catch (err) {
        console.log(err);
    }
}

What I am trying to test is, if like() and dislike() functions are being called depending on req.body.type. So here it is my test file.

const sinon = require('sinon');
const chai = require('chai');
const sinonChai = require("sinon-chai");
const mongoose = require('mongoose');

const seriesController = require('../../../../src/series-search/controllers/series.controller');
const Review = require('../../../../src/series-search/models/Review');

const expect = chai.expect;
chai.use(sinonChai);

let spyFindById, spyLike, spyDislike;

beforeEach(() => {
    spyLike = sinon.spy(Review.prototype, 'like');
    spyDislike = sinon.spy(Review.prototype, 'dislike');
    spyFindById = sinon.stub(Review, 'findById').returns({});
});

afterEach(() => {
    spyLike.restore();
    spyDislike.restore();
    spyFindById.restore();
});

describe('Series controller', () => {
    describe('search()', () => {

    });

    describe('addReview()', () => {
        it('should call findById() with review id');
    });

    describe('rateReview()', () => {
        it('should call review.like() if type is like', (done) => {
            const req = {
                body: {
                    id: '123456',
                    type: 'like'
                }
            };
            const res = {
                send: sinon.stub()
            };
            
            const spyLike = sinon.spy(review, 'like');
            seriesController.rateReview(req, res, null);

            expect(spyLike).to.have.been.calledOnce;
            done();
        });

        it('should call review.dislike() if type is dislike');
    });
});

The test keeps failing, as it says that expects 'like' to have been called once but it does not. I try a lot of things and search a lot on Google but I can't make it work. If anyone has any idea I would appreciate.

Thanks!


Solution

  • The correct way to do this is to return like and dislike when you stub findById method.

    ....
    
    let spyFindById, spyLike, spyDislike;
    
    beforeEach(() => {
      spyLike = sinon.spy();
      spyDislike = sinon.spy();
    
      // spy `like` and `dislike` here so our `review` variable can be spied. 
      // use `resolves` since it is promised based function
      spyFindById = sinon.stub(Review, 'findById').resolves({
        like: spyLike,
        dislike: spyDislike
      });
    });
    
    afterEach(() => {
      sinon.restore(); // use sinon.restore() is enough if you use the latest Sinon
    });
    
    describe('Series controller', () => {
      describe('rateReview()', () => {
    
        // no need to use `done` if we can use `async await`
        it('should call review.like() if type is like', async () => {
          const req = {
            body: {
              id: '123456',
              type: 'like'
            }
          };
          const res = {
            send: sinon.stub()
          };
    
          await seriesController.rateReview(req, res, null);
    
          expect(spyLike).to.have.been.calledOnce;
        });
    
        it('should call review.dislike() if type is dislike');
      });
    });