Nowadays, testing became an essential part of the modern software development process. However, the tools we're using for development often define some particularities in the testing setup. In this example we'll learn to write unit and functional tests for your Sails.js application.
First, define a connection to your test database. It can be as simple as adding
test: {
adapter: 'sails-memory'
}
to the module.exports.connections
object in ./config/connections.js
. Which
adapter you'll be using depends on how complex your application (and, hence,
tests) is: in simplest cases sails-memory
can work just fine, but in more
complex situations you may have to create a separate Mongo (or whatever else
you're using) database.
Use npm i --save-dev <package>
to add testing dependencies to your project.
In this example we're using the following packages:
- Mocha as the testing framework;
- Should.js as the assertion library;
- Sails-memory as the test database engine;
- Supertest for functional testing;
- Barrels for database fixtures.
Feel free to use these or replace them with your own favorites.
Add your test command to the scripts
section of package.json
. In our
case it'll be:
...
"scripts": {
...
"test": "PORT=9999 NODE_ENV=test mocha -R spec -b --recursive"
},
...
Here we are lifting our test Sails application on a non-standard port in case you'd like to run tests while the development version of your application is still running.
Finally, we got to the step where we have to write some tests. But first we are going to need our Sails application to be lifted while running the tests.
Let's create a ./test
folder (if it doesn't exist yet) and put a file
named bootstrap.test.js
in it. This file will contain global hooks for
setting up and tearing down our test environment. With Mocha, it will be
something like that:
var Sails = require('sails');
// Global before hook
before(function (done) {
// Lift Sails with test database
Sails.lift({
log: {
level: 'error'
},
models: {
connection: 'test',
migrate: 'drop'
}
}, function(err) {
if (err)
return done(err);
// Anything else you need to set up
// ...
done();
});
});
// Global after hook
after(function (done) {
console.log(); // Skip a line before displaying Sails lowering logs
Sails.lower(done);
});
Don't hesitate to add to these hooks any other steps specific to your application.
Given the setup we got, we can place our tests anywhere within the ./test
folder. However, it makes sense to organize them in a structure similar to
the one of our application.
Thus, skipping the api
folder (as more or less all the code we are supposed
to be testing resides within ./api
), we can put our model unit tests in
./test/models
.
For example, if we are testing a model called Apples
, we could put something
like this in ./test/models/Apples.test.js
:
describe('Apples', function() {
it ('should not be empty', function(done) {
Apples.find().exec(function(err, apples) {
apples.length.should.not.be.eql(0);
done();
});
});
});
The example in the previous section assumes that the test database contains
some data already. But if it gets dropped every time the tests finish
(which is the case when sails-memory
adapter is being used), you might want
to initialize your test database with some fixture data before you start
testing. There is a lot of ways to do it, but the idea is that you have to
populate your tables / collections in the global before
hook we put in
./test/bootstrap.test.js
:
before(function (done) {
Sails.lift({
log: {
level: 'error'
},
models: {
connection: 'test',
migrate: 'drop'
}
}, function(err) {
if (err)
return done(err);
// Load fixtures
var barrels = new Barrels();
// Populate the DB
barrels.populate(function(err) {
done(err);
});
});
});
In this example we are using Barrels
package, which reads fixture data from JSON files in a specified path
(./test/fixtures
by default) and populates the corresponding database
collections. Obviously, you can use any other fixture library or just
populate the test data in your own way, but the point is that before
hook is the place to do it.
Even though most of your code is supposed to reside within the models, writing functional tests for controllers is an important part of making your API solid and maintainable.
Following the convention we started with the model tests, let's put our code
in ./test/controllers/ApplesController.test.js
:
var request = require('supertest');
describe('ApplesController', function() {
describe('index', function() {
it('should return success', function (done) {
request(sails.hooks.http.app)
.get('/apples')
.expect(200, done);
});
});
});
In this (very basic) example we only check the status code, but, of course, you are free to do a more complex analysis of the response. Check out the Supertest API for more helpers and assertions.
Support is included for Wallaby.js in the form of
a bootstrap in the ./wallaby.js
file in the root that replicates what the
./test/bootstrap.test.
js does for normal mocha.
Wallaby.js is an intelligent test runner for JavaScript that continuously runs your tests. It reports code coverage and other results directly to your code editor immediately as you change your code. Wallaby.js uses various tricks to run your tests as fast as possible, such as dependency analysis to only execute tests affected by your code changes and parallel test execution.
Sails.js is a very powerful tool that can help you create APIs in almost no time. Still, if you have long term goals for your application, having properly written tests for your code is highly recommended. Here, we looked at some aspects of writing such tests and listed several useful packages that could help you, but feel free to add your own components, experiment with other testing frameworks or assertion libraries.
Copyright (c) 2013-2015 Ruslan Bredikhin