I am trying to test just the function of an express router with sinon. My tests below, the first test passes just fine no issues. The second however doesn't pass. I can't figure out why.
If I send an http request to the route it works as expected.
Something about the catch is causing issues. Below is code I was able to whittle it down to and the error
books.js
import express from 'express';
import models from '../db/models';
const router = express.Router();
var indexPost = async (req, res, next) => {
try {
let savedBook = await models.Book.create({
title: req.body.title || null,
isbn: req.body.isbn || null,
author: req.body.author || null
});
res.status(201).json({ book: savedBook.id });
} catch (err) {
res.status(400).send('');
}
};
router.post('/', indexPost);
export default router;
export { indexPost };
books.test.js
import { indexPost } from '../../../src/routes/books';
import models from '../../../src/db/models';
import sinon from 'sinon';
import { expect } from 'chai';
import sinonTestFactory from 'sinon-test';
const sinonTest = sinonTestFactory(sinon);
describe('Books router', () => {
describe('indexPost', () => {
it('should save the book to the database', sinonTest(async function () {
let req = {
body: {
title: 'Book Title',
isbn: '123asera23',
author: 123
}
};
let res = {
status: status => {},
json: json => {}
};
this.stub(res, 'status').returns(res);
this.stub(res, 'json').returns(res);
indexPost(req, res);
let book = await models.Key.findById(1);
expect(book.title).to.equal('Book Title');
expect(book.isbn).to.equal('123asera23');
expect(book.author).to.equal(123);
sinon.assert.calledWith(res.status, 201);
sinon.assert.calledWith(res.json, { book: 1 });
}));
it('should throw an error if data is not all there', sinonTest(async function () {
let req = {
body: {
title: 'Book Title',
author: 123
}
};
let res = {
status: status => {},
send: send => {}
};
this.stub(res, 'status').returns(res);
this.stub(res, 'send').returns(res);
indexPost(req, res);
sinon.assert.calledWith(res.status, 400);
sinon.assert.calledWith(res.send, '');
}));
});
});
Error
1) Books router
indexPost
should throw an error if data is not all there:
AssertError: expected status to be called with arguments
at Object.fail (/var/app/node_modules/sinon/lib/sinon/assert.js:96:21)
at failAssertion (/var/app/node_modules/sinon/lib/sinon/assert.js:55:16)
at Object.assert.(anonymous function) [as calledWith] (/var/app/node_modules/sinon/lib/sinon/assert.js:80:13)
at Context.<anonymous> (tests/src/routes/books.test.js:58:20)
at Generator.next (<anonymous>)
at step (tests/src/routes/books.test.js:21:191)
at tests/src/routes/keys.test.js:21:437
at new Promise (<anonymous>)
at Context.<anonymous> (tests/src/routes/books.test.js:21:99)
at callSandboxedFn (/var/app/node_modules/sinon-test/lib/test.js:94:25)
at Context.sinonSandboxedTest (/var/app/node_modules/sinon-test/lib/test.js:114:24)
Since no one looked at this to realize it wasn't as simple a unequal assertion I'll post the real answer I finally figured out.
Basically I was approaching the tests wrong. I was only half accounting for non-async code inside of javascript.
What I need to do was return a promise to mocha's it
function. In order to do that my controller needed to return a promise as well. In the case of doing database stuff I could just return the database call promise.
Once the promise calls either resolve
or reject
. You can then do your assertions to see if the tests work.
They key is you have to chain your promises from the bottom of your controller all the way back up to the it
function of mocha.
Below is the code to resolve this.
import express from 'express';
import models from '../db/models';
const router = express.Router();
var indexPost = (req, res, next) => {
return models.Book.create({
title: req.body.title || null,
isbn: req.body.isbn || null,
author: req.body.author || null
}).then(savedBook => {
res.status(201).json({ book: savedBook.id });
}).catch(err => {
res.status(400).send('');
});
};
router.post('/', indexPost);
export default router;
export { indexPost };
import { indexPost } from '../../../src/routes/books';
import models from '../../../src/db/models';
import sinon from 'sinon';
import { expect } from 'chai';
describe('Books router', () => {
describe('indexPost', () => {
it('should save the book to the database', async () => {
let req = {
body: {
title: 'Book Title',
isbn: '123asera23',
author: 123
}
};
const jsonStub = sinon.stub()
const res = { status: status => ({ json: jsonStub, send: err => err }) }
const statusSpy = sinon.spy(res, 'status')
return indexPost(req, res).then(() => {
let book = await models.Key.findById(1);
expect(book.title).to.equal('Book Title');
expect(book.isbn).to.equal('123asera23');
expect(book.author).to.equal(123);
sinon.assert.calledWith(res.status, 201);
sinon.assert.calledWith(res.json, { book: 1 });
})
}));
it('should throw an error if data is not all there', () => {
let req = {
body: {
title: 'Book Title',
author: 123
}
};
const sendStub = sinon.stub()
const res = { status: status => ({ json: err => err, send: sendStub }) }
const statusSpy = sinon.spy(res, 'status')
indexPost(req, res).then(() => {
sinon.assert.calledWith(res.status, 400);
sinon.assert.calledWith(res.send, '');
});
}));
});
});
I also had some weirdness in my response object the code above resolves that as well.