简体   繁体   中英

NodeJS: How to verify a function call inside promise resolution while testing using Mocha, Chai, Sinon?

I'm new to unit testing in NodeJS using Mocha, Chai and Sinon. I have been trying to write a unit test case for an API end-point and am struggling with testing if res.status(200).send(); is called from the API inside a Promise resolution. Below is the code:

controller.js - Has the function to be unit tested

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

module.exports = {
    getUserById(req, res) {
        const { params: reqParams } = req,
            { userId } = reqParams;

        User.findOne({ _id: userId })
            .exec()
            .then(function(userFromDb) {
                res.status(200).send();    // unable to test if this is getting called.
            })
            .catch((err) => res.status(500).send());
    }
};

test.js - Has the unit test

const sinon = require("sinon"),
    User = require("./user_model"),
    controller = require("../controller");

require("sinon-mongoose");

describe('users', function() {
    it('getUserById', function() {
        const validReqObj = {
            params: {
                userId: "123"
            }
        };

        const mock = sinon.mock(User);
        mock.expects("findOne").withArgs({ _id: validReqObj.params.userId })
            .chain("exec")
            .resolves({ _id: validReqObj.params.userId });

        const validRes = {
            status: function(statusCode) {
                sinon.assert.match(statusCode, 200);

                return {
                    send: function() {

                    }
                };
            }
        };

        controller.getUserById(validReqObj, validRes);

        mock.restore();
        mock.verify();
    });
});

As you can see, my test does 2 things:

  1. mocks the mongoose findOne function and tests if the promise resolution of findOne is fine or not.
  2. Asserts that res.status() gets called with a 200 status code.

It doesn't test if res.status() even gets called. For example, if I remove the line res.status(200).send(); from controller.js , the test still passes.

I tried creating spy on res.status . It doesn't work as expected.

The issue is your code has nothing to indicate when the asynchronous function has finished. While we could add a timer into the test to solve this, it's a bit crap solution.

The easiest way to solve this in a nice way is to return the promise from your function:

return User.findOne({ _id: userId })
       ....

After this, in your test you can simply do something like this:

return controller.getUserById(validReqObj, validRes).then(function() {
    mock.restore();
    mock.verify();
});

Note the return in the test as well - this allows mocha to detect it's an async test with a promise.

It's because getUserById is asynchronous function. Even when you've created mock for User model, getUserById is still asynchronous.

To create proper test, you can do the following:

  • return promise from getUserById function and create a promise chain it test.
  • use supertest module which to send a real http request and validate response.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM