Testing expressjs
Routes
This blog post approaches testing fairly large nodejs
application from a real-world perspective and with refactoring in mind. The use cases address advanced concepts that testing expressjs
routes are.
Automated testing of any JavaScript project is quite intimidating for newbies and veterans alike.
In this article we will talk about:
- Healthy test coverage of routes
- Modularization of routes for testability
- Mock Route's Request/Response Objects when necessary
- Mock requests to third-party endpoints such as Payment Gateway.
Additional challenges while testing expressjs
Routes*
- Test code, not the output
- Mock requests to Payment Gateway, etc.
- Mock database read/write operations
- Be able to cover exceptions and missing data structures
Even though this blog post was designed to offer complementary materials to those who bought my Testing
nodejs
Applications book, the content can help any software developer to tuneup working environment. You use this link to buy the book.
Show me the code
//
var User = require('./models').User;
module.exports = function getProfile(req, res, next){
User.findById(req.params.id, function(error, user){
if(error) return next(error);
return res.status(200).json(user);
});
};
//Router that Authentication Middleware
var router = require('express').Router();
var authenticated = require('./middleware/authenticated');
var getProfile = require('./settings/get-profile');
router.get('/profile/:id', authenticated, getProfile);
module.exports = router;
Example:
What can possibly go wrong?
When (unit) test expressjs
routes the following challenges may arise:
- Drawing a line between tests that fall into the unit testing category versus those tests that fall into the integration testing camp.
- Being mindful that authenticated routes can appeal in the picture
- Mock database read/write operations, or other layers(controller/service) that are not critical (core) to validation of the route's expectations
Choosing tools
If you haven't already, reading “How to choose the right tools” blog post gives insights on a framework we used to choose the tools we suggest in this blog.
Following our own Choosing the right tools framework, we suggest adopting the following tools, when testing expressjs
routes:
- We can technically have auto-reload or hot-reload using:
pm2
,nodemon
orforever
. We recommendsupervisor
. - We can choose amongst a myriad of test runners, for instance,
jasmine
(jasmine-node
),ava
orjest
. We recommendmocha
. The stackmocha
,chai
andsinon
can be worth it as well. supertest
framework for mocking Restful APIs andnock
for mocking HTTP.- Code under test is instrumented, but default reporting tools do not always suit our every project's needs. For test coverage reporting we recommend
istanbul
.
Workflow
It is possible to generate reports as tests progress.
latest versions of
istanbul
usesnyc
name.
# In package.json at "test" - add next line
> "istanbul test mocha -- --color --reporter mocha-lcov-reporter specs"
# Then run the tests using
$ npm test --coverage
Show me the test
If you haven't already, read the “How to write test cases developers will love”
The mainstream philosophy about automated testing is to write failing tests, followed by code that resolves the failing use cases. This is not always the case, especially when dealing with legacy code, or poorly tested code. The less puritan approach is at least tests when the code is still fresh in memory.
In this article, we assume the reader knows how to mock routes, otherwise there are articles that cover the basics of mocking routes' request/response objects and how to mock database read/write functions in this blog.
The common source of frustration and sometimes bad decision-making that follows is when not able to define boundaries: when to start refactoring, and when to stop.
Testing a route handler in isolation looks like testing any function. In our case, there should be a mocking operation of the User.findById()
function, that is intended to be used with the request.
For more on how to mock mongoose read/write function.
describe('getProfile', () => {
let req, res, next, error;
beforeEach(() => {
next = sinon.spy();
sessionObject = { ... };//mocking session object
req = { params: {id: 1234}, user: sessionObject };
res = { status: (code) => { json: sinon.spy() }}
});
it('returns a profile', () => {
getRequest(req, res, next);
expect(res.status().json).toHaveBeenCalled();
});
it('fails when no profile is found', () => {
getRequest(req, res, next);
expect(next).toHaveBeenCalledWith([error, null]);
});
});
Please refer to this article to learn more about how to mocking mongoose read/write functions.
Testing an integral route falls into the integration testing category. Whether we connect to a live database or use a live server route is up to the programmer, but the best(fast/efficient) approach is to mock out those two expensive parts as well.
var router = require('./profile/router'),
request = require('./support/http');
describe('/profile/:id', () => {
it('returns a profile', done => {
request(router)
.get('/profile/12')
.expect(200, done);
});
it('fails when no profile is found', done => {
request(router)
.get('/profile/NONEXISTENT')
.expect(500, done);
});
});
request = require('./support/http')
is the utility that may use either ofsupertest
ordupertest
provide a request.
Conclusion
When paying off technical debt, small bad moves can build up into catastrophe, such as downtime with little failure traceability. Good test coverage increase confidence when refactoring, refines boundaries, while at the same time reducing the introduction of new bugs in the codebase.
In this article, we reviewed how testing tends to be more of art, than science. We also stressed the fact that, like in any art, practice makes perfect ~ testing routes, just like testing controllers, can be challenging when interacting with external systems is involved. There are additional complimentary materials in the “Testing nodejs
applications” book.
References
- Testing
nodejs
Applications book nodejs
testing essentials ~ Fred K. Schott- Testing
expressjs
REST APIs withmocha
~ Elliot Blog - Marcus on
supertest
~ Marcus Soft Blog - “How to build and test REST with
nodejs
Express Mocha” ~ The Way of Code - A TDD Approach to Building a Todo API Using
nodejs
andmongodb
~ SemaphoreCI Community Tutorials