Reputation: 143
I'm trying to test a REST API built with express and mongoose, I'm using jest and supertest for the http calls; also I'm relatively new to testing with javascript.
When testing a creation url I wan't to make sure the instantiation is called using just the req.body object but I'm not sure how to do it, after reading a lot about differences between mock objects and stubs and some of the Jest documentation my last try looks like this:
test('Should instantiate the model using req.body', done => {
const postMock = jest.fn();
const testPost = {
name: 'Test post',
content: 'Hello'
};
postMock.bind(Post); // <- Post is my model
// I mock the save function so it doesn't use the db at all
Post.prototype.save = jest.fn(cb => cb(null, testPost));
// Supertest call
request(app).post('/posts/')
.send(testPost)
.then(() => {
expect(postMock.mock.calls[0][0]).toEqual(testPost);
done();
})
.catch(err => {throw err});
});
Also I would like to know how to manually fail the test on the promise rejection, so it doesn't throws the Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
Upvotes: 2
Views: 2103
Reputation: 22952
As it stands, you're performing more of a integration test rather than isolating the route handler function itself and testing just that.
First I would break away the handler for /posts/
to its own file (assuming you haven't done this already):
controllers/post-controller.js
const Post = require('./path/to/models/post')
exports.store = async (req, res) => {
const post = await new Post(req.body).save()
res.json({ data: post }
}
Next simply use the handler wherever you defined your routes:
const express = require('express')
const app = express()
const postController = require('./path/to/controllers/post-controller')
app.post('/posts', postController.store)
With this abstraction we can now isolate our postController.store
and test that it works with req.body
. Now since we need to mock mongoose to avoid hitting an actual database, you can create a mocked Post
like so (using the code you already have):
path/to/models/__mocks__/post.js
const post = require('../post')
const mockedPost = jest.fn()
mockedPost.bind(Post)
const testPost = {
name: 'Test post',
content: 'Hello'
}
Post.prototype.save = jest.fn(cb => {
if (typeof cb === 'function') {
if (process.env.FORCE_FAIL === 'true') {
process.nextTick(cb(new Error(), null))
} else {
process.nextTick(cb(null, testPost))
}
} else {
return new Promise((resolve, reject) => {
if (process.env.FORCE_FAIL === 'true') {
reject(new Error())
} else {
resolve(testPost)
}
})
}
})
module.exports = mockedPost
Notice the check for process.env.FORCE_FAIL
if for whatever reason you wanted to fail it.
Now we're ready to test that using the req.body
works:
post-controller.test.js
// Loads anything contained in `models/__mocks__` folder
jest.mock('../location/to/models')
const postController = require('../location/to/controllers/post-controller')
describe('controllers.Post', () => {
/**
* Mocked Express Request object.
*/
let req
/**
* Mocked Express Response object.
*/
let res
beforeEach(() => {
req = {
body: {}
}
res = {
data: null,
json(payload) {
this.data = JSON.stringify(payload)
}
}
})
describe('.store()', () => {
test('should create a new post', async () => {
req.body = { ... }
await postController(req, res)
expect(res.data).toBeDefined()
...
})
test('fails creating a post', () => {
process.env.FORCE_FAIL = true
req.body = { ... }
try {
await postController.store(req, res)
} catch (error) {
expect(res.data).not.toBeDefined()
...
}
})
})
})
This code is untested, but I hope it helps in your testing.
Upvotes: 7