Search code examples
javascriptnode.jsexpresssinonsupertest

Why does sinon stub not replacing the actual exports.function


I have a controller async function which calls another async exported function and instead of testing the dependency I just want to test specific results of that dependency function. However, when I stub the function nothing happens and the return result is as if I never stubbed the function in the first place.

exports.getUser = async (req, res) => {
    try {
        let user = null; 

        if(req && req.params.id) {
            const id = parseInt(req.params.id, 10);
            if(isNaN(id)) {
                return res.status(500).json({ message: 'Invalid request.' });
            }

            user = await userService.getUserById(id);

            if(!products) {
                return res.status(404).json({ message: `user does not exist for the given id` });
            }
        } else {
            user = await userService.getUser();
            if(!user || user.length === 0) {
                return res.status(500).json({ message: 'user not available' });
            }
        }
        res.jsonp({user});
    } catch(e) {
        return res.status(500).json({message: e.message});
    }
}

And now i am trying to stub the above function. But my stub is not working.

the test file:

const expect = require("chai").expect;
const request = require("supertest");
const server = require('../../server');
const sinon = require('sinon');
const userController = require('../../contollers/user');

describe('GET /v1/user', () => {
        let userControllerStub;
        beforeEach(() => {            
            userControllerStub = sinon
                .stub(userController, 'getUser')
                .resolves({getUser: [{ id: 123, name: 'xxxxx' }]});
        })

        afterEach(() => {
            userControllerStub.restore();
        });
        it('Should return list of users', (done) => {
            try {
                request(server).get('/v1/user')
                .end((err, res) => {
                    if (err) {
                        done(err);
                    }
                    console.log('res', res.body);
                    expect(res.statusCode).to.equal(200);

                    //expect(res.body.user).to.have.lengthOf(4); here i am expetcing stub should return the value
                    done();
                });
            } catch (err) {
                done(err)
            }

        });
    });

Any help much appreciated.


Solution

  • Based on testing pyramid, I can answer on 2 level.

    1. Context unit test.

    I suggest you create this test first before you go to next point. You need test directly to function getUser. When you test that function, you need to stub userService.getUserById or userService.getUser depends on your case.

    Example:

    const sinon = require('sinon');
    
    // Need to load user controller, the focus of the unit test.
    const userController = require('../../controller/user');
    
    // Need to load userServer for stub purpose.
    // Note: I do not know whether this path is correct.
    const userService = require('../../services/user');
    
    
    describe('Controller User', function () {
      describe('getUser', async function () {
        it('with request param id', function () {
          const stubUserServGetUserById = sinon.stub(userService, 'getUserById');
          stubUserServGetUserById.resolves({ user: 'xxx' });
    
          // Prepare the arguments.
          const req = { params: { id: 1 } };
          // Example: fake all resp. (or you can get the real resp)
          const resp = {
            status: sinon.fake(),
            json: sinon.fake(),
            jsonp: sinon.fake(),
          };
    
          // Called getUser directly.
          const result = await userController.getUser(req, resp);
    
          // Example: Expect the result.
          expect(result).to.equal();
          // Expect whether stub get called.
          expect(stubUserServGetUserById.calledOnce).to.equal(true);
          // Also you can validate whether fake function at resp get called if you need.
    
          // Do not forget to restore the stub.
          stubUserServGetUserById.restore();
        });
    
        it ('without request apram id', function () {
          const stubUserServGetUser = sinon.stub(userController, 'getUser');
          stubUserServGetUser.resolves({ user: 'xxx' });
    
          // Prepare the arguments.
          const req = { params: {} };
          // Example: fake all resp. (or you can get the real resp)
          const resp = {
            status: sinon.fake(),
            json: sinon.fake(),
            jsonp: sinon.fake(),
          };
    
          // Called getUser directly.
          const result = await userController.getUser(req, resp);
    
          // Example: Expect the result.
          expect(result).to.equal();
          // Expect whether stub get called.
          expect(stubUserServGetUser.calledOnce).to.equal(true);
          // Also you can validate whether fake function at resp get called if you need.
    
          // Do not forget to restore the stub.
          stubUserServGetUser.restore();
        });
      });
    });
    
    
    1. Context service test.

    This is exactly what your are trying to do in your sample test file above. But you need to know that testing user v1 service not only involves controller getUser but also other controller, which I do not know. On this test level, I suggest you setup dummy user data. Add dummy data before test, and remove dummy data after test. Then you add validation whether controller getUser get called. Or, you can change your stub from userController.getUser to userService.getUser (because you know that getUser will get called based on unit test before). No sample here. Sorry.

    Hope this helps.