Testing nodejs
background jobs
The majority of web applications may not need a background job, but for those that do, experience some level of shadow around testing/debugging and discovering issues before it becomes too late. This article contributes towards increasing testability and saving time for late debugging.
As it was in other blogs that preceded this one, we will explore some of the ways to make sure most of the parts are accessible for testability.
In this article we will talk about:
- Aligning background jobs with unit test best practices
- Mocking session data for services that need authentication
- Mocking third party systems when testing a background job
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
//Job Definition in jobs/email.js
var email = require('some-lib-to-send-emails');
var User = require('./models/user.js');
module.exports = function(agenda) {
agenda.define('registration email', function(job, done) {
User.findById(job.attrs.data.userId, function(err, user) {
if(err) return done(err);
var message = ['Thanks for registering ', user.name, 'more content'].join('');
return email(user.email, message, done);
});
});
agenda.define('reset password', function(job, done) {/* ... more code*/});
// More email related jobs
};
//triggering in route.js
//lib/controllers/user-controller.js
var app = express(),
User = require('../models/user-model'),
agenda = require('../worker.js');
app.post('/users', function(req, res, next) {
var user = new User(req.body);
user.save(function(err) {
if(err) return next(err);
//@todo - Schedule an email to be sent before expiration time
//@todo - Schedule an email to be sent 24 hours
agenda.now('registration email', { userId: user.primary() });
return res.status(201).json(user);
});
});
Example:
What can possibly go wrong?
When trying to figure out how to approach testing delayed asynchronous nodejs
background jobs, the following points may be a challenge:
It is easy to fall into the integration testing trap when testing nodejs
background jobs. Not only those jobs are asynchronous, but also are scheduled to run at a particular time. The following are additional challenges when testing nodejs
background jobs in a Unit Test context.
- Testing asynchronous jobs in a synchronous context ~ time-bound constraints may not be predictable, therefore not covered with our tests
- Identifying and choosing the right break-point to do the mocking/stubbing
- Mock third-party services such as Payment Gateway, etc.
- Mock database read/write operations
- Sticking to unit testing good practices
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 nodejs
background, or scheduled, tasks:
- 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. - 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
What should I be testing
If you haven't already, read the “How to write test cases developers will love”
Istanbul generates reports as tests progress.
# 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
Example:
Show me the tests
If you haven't already, read the “How to write test cases developers will love”
It is a little bit challenging to test a function that is not accessible outside its definition closure. However, making the function definition accessible from outside the library makes it possible to test the function in isolation.
describe('Jobs', () => {
it('should define registration email', done => {
registrationEmailTask(params, (attrs) => {
expect(User.findById).toHaveBeenCalled();
expect(email).toHaveBeenCalled();
done();
});
});
});
Following the same footsteps, we can test the
reset password
task. To learn more about mocking database functions, please read this article.
There is a chapter on testing background jobs in the book, for more techniques to mock, modularize and test background jobs.
The lens to test the application from counts more at this level. A misstep makes us fall into integration testing territory, un-willingly.
Conclusion
Automated testing of any JavaScript project is quite intimidating for newbies and veterans alike. 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 background jobs constitutes some of the challenging tasks from the asynchronous nature of the jobs. There are additional complimentary materials in the “Testing nodejs
applications” book.