Serial Expresso Testing with Mongo Fixtures
Expresso is a test runner for Node.js. It boasts “high speed parallel testing”. It means it can run your whole test suite of a couple of thousands of tests in less than a second… but this post is not about that. This post is about taking it slow, and running tests one by one like you might be used to from other environments (Python, anyone?).
Isn’t slow… err… bad?
So, why use a high-speed parallelized test runner to run tests slowly and serially? There are some cases where this might be useful.
In particular, serial testing is very useful when running tests with database operations where you have no transaction support. In such a case, database operation in one test (for example, removal of a record) migth conflict with operation in another test (for example, fetching of a record that should have been removed). Database operations take time, and on a non-blocking platform such as Node.js, this leads all sorts of problems if you cannot allow operations to end before next ones are executed.
Regardless of how much you value speed, sometimes hasty means wrong test results. For the past 4 or 5 days, I’ve battled the non-blocking nature of Node.js to get a good work flow for testing database-related operations in an app that I’m writing, and here I’ll recap my final solution.
The basic setup
Here’s a basic setup for the test module.
var mongoose = require('mongoose'),
db = mongoose.connect('mongodb://localhost/mymodeltest'),
fixture = require('../helpers/fixture'),
MySchema = require('../../models/mymode).MySchema,
Model,
test = exports;
Let’s assume that tests are all located in appdir/tests/unit. This test will be located in appdir/tests/unit/mymodel.tests.js. I’ll also use Mongoose ORM for this test. If you don’t use Mongoose, you will find the fixture module uninteresting, but it still might give you an idea about how to make your own setup.
For the moment, ignore the var statements above. You can look at it later as we progress to see what comes from where. Just note that we have aliased exports as test to make the module look a bit nicer, but that’s completely optional.
Also note that we are using a seaparate database for this unit test. I generally opt to use separate database for each unit, and only use a project-wide database for integration testing. This allows me to run multiple unit test modules at once in parallel (even though individual tests in each module are run serially), which speeds things up a little.
First thing first:
Model = db.model('Model', MySchema);
This sets up the model to use the connection. Next, you need to add a few test fixtures. I usually specify fixtures as an array of objects. The objects are formulated in a way that will make a whole document if passed to the model constructor. In our example, if I pass such object as new Model(testData[0]), it should be ready to save without any further intervention. So we specify the fixtures like so:
testData = [
{ ... },
{ ... },
{ ... }
];
This is the minimal setup that will get us started.
fixture module
You can get the code for the fixture module from a gist. (I give you permission to use, modify, copy, and redistribute, yada yada… IOW, I won’t sue.) It’s a single method which cleans up the database, loads the fixtures specified as an array (as discussed in previous section), and allows you to specify a callback that will be executed once all fixtures are loaded.
Your callback may accept data, and another callback (I usually call it next like in middlewares). Data is a collection of document objects that were inserted into the database. The next callback cleans up the database once your tests are run, if you choose to call it when you’re done testing. The next callback will also take a callback that… is it too confusing? Ok, let’s just put down basics and move on.
fixture.load(Model, testData, function(data, next) {
// Your tests go here
});
Oh, and the load path for this was ../helpers/fixture, because I keep the test helpers in appdir/tests/helpers directory. You should adjust the path according to your situation.
Using exports['test name'] = function() {...} format
As you know, Expresso allows you to specify tests in one of two ways. One way is to specify all your tests in an object literal:
module.exports = {
'test1': function() {...},
'test2': function() {...},
'test3': function() {...}
};
But… you cannot use this in this scenario. You have to use the other format:
exports['test1'] = function() {...};
exports['test2'] = function() {...};
exports['test3'] = function() {...};
As you rememeber we have aliased exports as test, so you can do this:
test['test1'] = function() {...};
test['test2'] = function() {...};
test['test3'] = function() {...};
If you use the latter method, you can specify tests withing a callback of an asynchronous operation (such as inserting fixtures, right?), and that’s exactly what we’re aiming for.
fixture.load(Model, testData, function(data, next) {
test['test1'] = function() {...};
test['test2'] = function() {...};
test['test3'] = function() {...};
});
Using the before-exit hook
All test functions accept a before-exit callback that you can use to tell Expresso when your test is done. Do use them do this:
test['my asynchronous test'] = function(exit) {
Model.find(function(err, docs) {
// test data
exit(); // Calls exit to signify that test is done.
});
};
Tearing down
Finally, after all your tests are run, it’s time to destroy the test data and disconnect from the database. Disconnecting from the database allows Expresso to terminate correctly instead of you having to terminate it manually with Ctrl+C.
Tear-down is done by adding one more test to the end of your test module that will call the next callback from fixture.load call, and disconnect the database. I usually call this last test simply end.
fixture.load(Model, testData, function(data, next) {
test['test1'] = function() {...};
test['test2'] = function() {...};
test['test3'] = function() {...};
// ... more tests, and finally:
test.end = function(exit) {
next(function() { // ``next`` is from the ``fixture.load`` call
db.disconnect(exit); // We let ``db.disconnect`` call exit
});
};
});
As noted in the comments next to the code, db.disconnect call takes a callback which is called when the connection to database is closed. We conveniently pass the before-exit callback to it. This is done within a call to next. The callback function we passed to next will be executed once all test fixtures are removed from the database.
Running the tests
To run the tests serially, you can use the -s switch:
expresso -s tests/unit/*.tests.js
Aaand… that’s it. Happy serial killing… err… I mean testing.




