Yamona
Yamona

Reputation: 1130

Express: Unit test controller which call model mongoose model without really call the database

I've a controller called by an Express.js router. The controller itself call mongoose model. I need to unit test the controller without calling the database and independent from the router.

Test probably should test the res.json(err) and res.json(result) response.

Page controller:

const pageModel = require('../model/page');

const pageController = {
    /**
     * Get all pages as a list
     * 
     * @param {*} req 
     * @param {*} res 
     * @returns {json} list of pages
     */
    getList(req, res) {
        pageModel.find({}, (err, result)=>{
            if (err) {
                res.json(err);
              } else {
                res.json(result);
              }
        });
    }
};

module.exports = pageController;

Page model:

const mongoose = require('mongoose');

// Page Schema
const pageSchema = new mongoose.Schema({
    title: {
        type: String,
        required: true
    },
    slug: {
        type: String,
        required: true,
        unique: true
    },
    content: String,
    createdIn: {
        type: Date,
        default: Date.now
    },
    updatedIn: {
        type: Date,
        default: Date.now
    }
});

const pageModel = mongoose.model('page', pageSchema);
module.exports = pageModel;

Test:

const chai = require('chai');
const expect = chai.expect;
const mongoose = require('mongoose');
const PageModel = require('../../../../app/page/model/page');
const pageController = require('../../../../app/page/controller/page');
const sinon = require('sinon');

let sandbox = sinon.createSandbox();

describe('Page controller', () => {

    describe('getList', () => {
        it('should return result as json object', (done) => {
            
        });
    });
});

Upvotes: 0

Views: 365

Answers (1)

Yamona
Yamona

Reputation: 1130

Here is an answer to my question:

First I modified the controller function getList to send error status

getList(req, res) {
    PageModel.find({}, (err, result) => {
        if (err) {
            res.status(500).end();
        } else {
            res.json(result);
        }
    });
}

Now, test will test 2 things. 1- A result which return an instance of "Page" which is json object. 2- Error

Explanation:

1- Create a sinon sandbox

let sandbox = sinon.createSandbox();

2- Mock and define: req, res, error ..etc.

let req = {
    body: {
        title: 'title',
        slug: 'slug',
        content: 'content'
    }
};
let res = {};
let expectedResult;
let error = new Error({error: 'error message here'});

3- Before each test, spy on res.json and res.status (error) and create the expected result (optional). After each test restore the sandbox.

beforeEach(()=>{
    res = {
        json: sandbox.spy(),
        status: sandbox.stub().returns({end: sandbox.spy()})
    };
    expectedResult = [{}, {}, {}];
});

afterEach(()=>{
    sandbox.restore();
});

4- In test. Stub mongoose.Model not PageModel. This way you can be sure that mongoose will not call the database (This is important as we don't do integration test). Then make sure that PageModel has been called and the controller function return the expected result.

it('should return result/page as json object', (done) => {
    sandbox.stub(mongoose.Model, 'find').yields(null, expectedResult);
    PageController.getList(req, res);

    sinon.assert.calledWith(PageModel.find, {});
    sinon.assert.calledWith(res.json, sinon.match.array);
    done();
});

it('should return error 500', (done) => {
    sandbox.stub(mongoose.Model, 'find').yields(error);
    PageController.getList(req, res);

    sinon.assert.calledWith(PageModel.find, {});
    sinon.assert.calledWith(res.status, 500);
    sinon.assert.calledOnce(res.status(500).end);
    done();
});

Here is the full code:

const chai = require('chai');
const expect = chai.expect;
const mongoose = require('mongoose');
const PageModel = require('../../../../app/page/model/page');
const PageController = require('../../../../app/page/controller/page');
const sinon = require('sinon');

let sandbox = sinon.createSandbox();

describe('Page controller', () => {
    let req = {
        body: {
            title: 'title',
            slug: 'slug',
            content: 'content'
        }
    };
    let res = {};
    let expectedResult;
    let error = new Error({error: 'error message here'});
    
    describe('getList', () => {
        beforeEach(()=>{
            res = {
                json: sandbox.spy(),
                status: sandbox.stub().returns({end: sandbox.spy()})
            };
            expectedResult = [{}, {}, {}];
        });
    
        afterEach(()=>{
            sandbox.restore();
        });
    

        it('should return result/page as json object', (done) => {
            sandbox.stub(mongoose.Model, 'find').yields(null, expectedResult);
            PageController.getList(req, res);

            sinon.assert.calledWith(PageModel.find, {});
            sinon.assert.calledWith(res.json, sinon.match.array);
            done();
        });

        it('should return error 500', (done) => {
            sandbox.stub(mongoose.Model, 'find').yields(error);
            PageController.getList(req, res);

            sinon.assert.calledWith(PageModel.find, {});
            sinon.assert.calledWith(res.status, 500);
            sinon.assert.calledOnce(res.status(500).end);
            done();
        });
    });
});

Please if you find an improvement to this answer feel free to edit the answer or write your own answer

Upvotes: 1

Related Questions