I\'m in the process of learning Node.js and have been playing around with Express. Really like the framework;however, I\'m having trouble figuring out how to write a unit/i
I was wondering this as well, but specifically for unit tests and not integration tests. This is what I'm doing right now,
test('/api base path', function onTest(t) {
t.plan(1);
var path = routerObj.path;
t.equals(path, '/api');
});
test('Subrouters loaded', function onTest(t) {
t.plan(1);
var router = routerObj.router;
t.equals(router.stack.length, 5);
});
Where the routerObj is just {router: expressRouter, path: '/api'}
.
I then load in subrouters with var loginRouterInfo = require('./login')(express.Router({mergeParams: true}));
and then the express app calls an init-function taking in the express router as a parameter. The initRouter then calls router.use(loginRouterInfo.path, loginRouterInfo.router);
to mount the subrouter.
The subrouter can be tested with:
var test = require('tape');
var routerInit = require('../login');
var express = require('express');
var routerObj = routerInit(express.Router());
test('/login base path', function onTest(t) {
t.plan(1);
var path = routerObj.path;
t.equals(path, '/login');
});
test('GET /', function onTest(t) {
t.plan(2);
var route = routerObj.router.stack[0].route;
var routeGetMethod = route.methods.get;
t.equals(routeGetMethod, true);
var routePath = route.path;
t.equals(routePath, '/');
});
Change your response object:
var response = {
viewName: ""
, data : {}
, render: function(view, viewData) {
this.viewName = view;
this.data = viewData;
}
};
And it will work.
To achieve unit testing instead of integration testing, I mocked the response object of the request handler.
/* app.js */
import endpointHandler from './endpointHandler';
// ...
app.post('/endpoint', endpointHandler);
// ...
/* endpointHandler.js */
const endpointHandler = (req, res) => {
try {
const { username, location } = req.body;
if (!(username && location)) {
throw ({ status: 400, message: 'Missing parameters' });
}
res.status(200).json({
location,
user,
message: 'Thanks for sharing your location with me.',
});
} catch (error) {
console.error(error);
res.status(error.status).send(error.message);
}
};
export default endpointHandler;
/* response.mock.js */
import { EventEmitter } from 'events';
class Response extends EventEmitter {
private resStatus;
json(response, status) {
this.send(response, status);
}
send(response, status) {
this.emit('response', {
response,
status: this.resStatus || status,
});
}
status(status) {
this.resStatus = status;
return this;
}
}
export default Response;
/* endpointHandler.test.js */
import Response from './response.mock';
import endpointHandler from './endpointHander';
describe('endpoint handler test suite', () => {
it('should fail on empty body', (done) => {
const res = new Response();
res.on('response', (response) => {
expect(response.status).toBe(400);
done();
});
endpointHandler({ body: {} }, res);
});
});
Then, to achieve integration testing, you can mock your endpointHandler and call the endpoint with supertest.
In my case the only I wanted to test is if the right handler has been called. I wanted to use supertest to laverage the simplicity of making the requests to the routing middleware. I am using Typescript a and this is the solution that worked for me
// ProductController.ts
import { Request, Response } from "express";
class ProductController {
getAll(req: Request, res: Response): void {
console.log("this has not been implemented yet");
}
}
export default ProductController
The routes
// routes.ts
import ProductController from "./ProductController"
const app = express();
const productController = new ProductController();
app.get("/product", productController.getAll);
The tests
// routes.test.ts
import request from "supertest";
import { Request, Response } from "express";
const mockGetAll = jest
.fn()
.mockImplementation((req: Request, res: Response) => {
res.send({ value: "Hello visitor from the future" });
});
jest.doMock("./ProductController", () => {
return jest.fn().mockImplementation(() => {
return {
getAll: mockGetAll,
};
});
});
import app from "./routes";
describe("Routes", () => {
beforeEach(() => {
mockGetAll.mockImplementation((req: Request, res: Response) => {
res.send({ value: "You can also change the implementation" });
});
});
it("GET /product integration test", async () => {
const result = await request(app).get("/product");
expect(mockGetAll).toHaveBeenCalledTimes(1);
});
it("GET an undefined route should return status 404", async () => {
const response = await request(app).get("/random");
expect(response.status).toBe(404);
});
});
I had some issues to get the mocking to work. but using jest.doMock and the specific order you see in the example makes it work.
As others have recommended in comments, it looks like the canonical way to test Express controllers is through supertest.
An example test might look like this:
describe('GET /users', function(){
it('respond with json', function(done){
request(app)
.get('/users')
.set('Accept', 'application/json')
.expect(200)
.end(function(err, res){
if (err) return done(err);
done()
});
})
});
Upside: you can test your entire stack in one go.
Downside: it feels and acts a bit like integration testing.
if unit testing with express 4 note this example from gjohnson :
var express = require('express');
var request = require('supertest');
var app = express();
var router = express.Router();
router.get('/user', function(req, res){
res.send(200, { name: 'tobi' });
});
app.use(router);
request(app)
.get('/user')
.expect('Content-Type', /json/)
.expect('Content-Length', '15')
.expect(200)
.end(function(err, res){
if (err) throw err;
});