Testing Mongoose with Jest
Jest is a client-side JavaScript testing library developed by Facebook. Because Jest is designed primarily for testing React applications, using it to test Node.js server-side applications comes with a lot of caveats. We strongly advise against using Jest for testing any Node.js apps unless you are an expert developer with an intimate knowledge of Jest.
If you choose to delve into dangerous waters and test Mongoose apps with Jest, here's what you need to know:
Recommended testEnvironment
Do not use Jest's default jsdom
test environment
when testing Mongoose apps, unless you are explicitly testing an
application that only uses
Mongoose's browser library.
The jsdom
test environment attempts to create a browser-like test
environment in Node.js, and it comes with numerous nasty surprises like a
stubbed setTimeout()
function
that silently fails after tests are finished. Mongoose does not support jsdom
in general and is not expected to function correctly in the jsdom
test
environment.
To change your testEnvironment
to Node.js, add testEnvironment
to your
jest.config.js
file:
module.exports = {
testEnvironment: 'node'
};
Timer Mocks
Absolutely do not use timer mocks
when testing Mongoose apps. Fake timers stub out global functions like
setTimeout()
and setInterval()
, which causes problems when an underlying
library uses these functions. The MongoDB Node.js driver uses these functions
for deferring work until the next tick of the event loop and for monitoring
connections to the MongoDB server.
Mongoose devs have already refactored out code
to avoid using setImmediate()
to defer work to the next tick of the event loop, but we can't reasonably
ensure that every library Mongoose depends on doesn't use setImmediate()
.
Mongoose uses nextTick()
, which Jest's underlying dependency explicitly doesn't stub by default.
But, process.nextTick()
isn't the same as setImmediate()
because of
microtasks vs macrotasks,
so underlying libraries like the MongoDB driver may use setImmediate()
.
To work around this, create your own wrapper around setTimeout()
and
stub that instead using sinon.
// time.js
exports.setTimeout = function() {
return global.setTimeout.apply(global, arguments);
};
// Tests
const time = require('../util/time');
const sinon = require('sinon');
sinon.stub(time, 'setTimeout');
globalSetup
and globalTeardown
Do not use globalSetup
to call mongoose.connect()
or
mongoose.createConnection()
. Jest runs globalSetup
in
a separate environment,
so you cannot use any connections you create in globalSetup
in your tests.
Further Reading
Want to learn more about testing Mongoose apps? The RESTful Web Services with Node.js and Express course on Pluralsight has a great section on testing Mongoose apps with Mocha.