javascriptunit-testingpromisemockingproxyquire

Node.js - unit test for function with mocked promise inside


I'm currently making a small server in JavaScript and as part of the learning process I'm writing unit tests for the functions. Unfortunately I ran into major difficulties with a certain test that handles a promise. Below is the router module, with a separate handlePUT function for ease of testing.

const express = require('express');
const service = require('../service/user.service');

const dutyStatusRouter = express.Router();
const output = console;

function handlePUT(req, res) {
    service.updateUserStatus()
        .then((fulfilled) => {
            res.status(fulfilled);
            res.send();
        })
        .catch(() => {
            res.status(500);
            res.send();
        });
}

dutyStatusRouter.route('/').put(handlePUT);

The updateUserStatus function basically toggles a Boolean in a database and looks somewhat like this:

function updateUserStatus() {
    return new Promise((resolve, reject) => {
        if (…) {
            resolve(201);
        } else if (…) {
            resolve(200);
        } else {
            reject();
        }
    });
}

As for the unit tests, I'm using mocha/chai, with proxyquire to create a mock updateUserStatus.

const chai = require('chai');
const sinon = require('sinon');
const proxyquire = require('proxyquire');

const serviceStub = {};

describe('=== Unit test ===', () => {
    it('Handle PUT test: promise kept', async () => {
        const dutyStatusRouter = proxyquire('../../router/duty-status.router', {
            '../service/user.service': serviceStub,
        });
        serviceStub.updateUserStatus = () => {
            return new Promise((resolve, reject) => {
                resolve(200);
            });
        };
        const res = {
            status: sinon.fake(),
            send: sinon.fake(),
        };
        await dutyStatusRouter.handlePUT({}, res);
        chai.assert(res.status.calledOnceWith(200));
    });
});

Whenever I try to run the unit test, I get the error Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.. If I try to add done() it still fails by giving the error message Error: Resolution method is overspecified. Specify a callback *or* return a Promise; not both.


Solution

  • Found a solution that works, so I'm adding it here:

    const chai = require('chai');
    const sinon = require('sinon');
    const proxyquire = require('proxyquire');
    
    const serviceStub = {};
    const dutyStatusRouter = proxyquire('../../router/duty-status.router', {
        '../service/user.service': serviceStub,
    });
    
    describe('=== Unit test ===', () => {
        it('Handle PUT test: promise kept', (done) => {
            serviceStub.updateUserStatus = sinon.stub().resolves(200);
            const res = {
                status: sinon.fake(),
                send: sinon.fake(),
            };
            dutyStatusRouter.handlePUT({}, res).then(() => {
                chai.assert(res.status.calledWith(200));
                done();
            });
        });
    });
    

    Note: I changed the handlePUT function just a bit, it now looks like this (I just added a return):

    function handlePUT(req, res) {
        return service.updateUserStatus()
            .then((fulfilled) => {
                output.log('Promise fulfilled');
                res.status(fulfilled);
                res.send();
            })
            .catch(() => {
                output.log('Promise unfulfilled');
                res.status(500);
                res.send();
            });
    }