Mocha is used as the test framework along with should.js
and supertest. The principles behind why we use testing in our apps along with some basics on Mocha are covered in Chapter 1, Building a Basic Express Site. Testing supertest lets you test your HTTP assertions and testing API endpoints.
The tests are placed in the root folder /test
. Tests are completely separate from any of the source code and are written to be readable in plain English, that is, you should be able to follow along with what is being tested just by reading through them. Well-written tests with good coverage can serve as a readme for its API, since it clearly describes the behavior of the entire app.
The initial setup to test our movies API is the same for both /test/actors.js
and /test/movies.js
and will look familiar if you have read Chapter 1, Building a Basic Express Site:
var should = require('should'); var assert = require('assert'); var request = require('supertest'); var app = require('../src/lib/app');
In src/test/actors.js
, we test the basic CRUD operations: creating a new actor object, retrieving, editing, and deleting the actor object. An example test for the creation of a new actor is shown as follows:
describe('Actors', function() { describe('POST actor', function(){ it('should create an actor', function(done){ var actor = { 'id': '1', 'name': 'AxiomZen', 'birth_year': '2012', }; request(app) .post('/actors') .send(actor) .expect(201, done) });
We can see that the tests are readable in plain English. We create a new POST
request for a new actor to the database with the id
of 1
, name
of AxiomZen
, and birth_year
of 2012
. Then, we send the request with the .send()
function. Similar tests are present for GET
and DELETE
requests as given in the following code:
describe('GET actor', function() { it('should retrieve actor from db', function(done){ request(app) .get('/actors/1') .expect(200, done); }); describe('DELETE actor', function() { it('should remove a actor', function(done) { request(app) .delete('/actors/1') .expect(204, done); }); });
To test our PUT
request, we will edit the name
and birth_year
of our first actor as follows:
describe('PUT actor', function() { it('should edit an actor', function(done) { var actor = { 'name': 'ZenAxiom', 'birth_year': '2011' }; request(app) .put('/actors/1') .send(actor) .expect(200, done); }); it('should have been edited', function(done) { request(app) .get('/actors/1') .expect(200) .end(function(err, res) { res.body.name.should.eql('ZenAxiom'), res.body.birth_year.should.eql(2011); done(); }); }); });
The first part of the test modifies the actor name
and birth_year
keys, sends a PUT
request for /actors/1
(1
is the actors id
), and then saves the new information to the database. The second part of the test checks whether the database entry for the actor with id
1
has been changed. The name
and birth_year
values are checked against their expected values using .should.eql()
.
In addition to performing CRUD actions on the actor object, we can also perform these actions to the movies we add to each actor (associated by the actor's ID). The following snippet shows a test to add a new movie to our first actor (with the id
of 1
):
describe('POST /actors/:id/movies', function() { it('should successfully add a movie to the actor',function(done) { var movie = { 'id': '1', 'title': 'Hello World', 'year': '2013' } request(app) .post('/actors/1/movies') .send(movie) .expect(201, done) }); }); it('actor should have array of movies now', function(done){ request(app) .get('/actors/1') .expect(200) .end(function(err, res) { res.body.movies.should.eql(['1']); done(); }); }); });
The first part of the test creates a new movie object with id
, title
, and year
keys, and sends a POST
request to add the movies as an array to the actor with id
of 1
. The second part of the test sends a GET
request to retrieve the actor with id
of 1
, which should now include an array with the new movie input.
We can similarly delete the movie entries as illustrated in the actors.js
test file:
describe('DELETE /actors/:id/movies/:movie_id', function() { it('should successfully remove a movie from actor', function(done){ request(app) .delete('/actors/1/movies/1') .expect(200, done); }); it('actor should no longer have that movie id', function(done){ request(app) .get('/actors/1') .expect(201) .end(function(err, res) { res.body.movies.should.eql([]); done(); }); }); });
Again, this code snippet should look familiar to you. The first part tests that sending a DELETE
request specifying the actor ID and movie ID will delete that movie entry. In the second part, we make sure that the entry no longer exists by submitting a GET
request to view the actor's details where no movies should be listed.
In addition to ensuring that the basic CRUD operations work, we also test our schema validations. The following code tests to make sure two actors with the same ID do not exist (IDs are specified as unique):
it('should not allow you to create duplicate actors', function(done) { var actor = { 'id': '1', 'name': 'AxiomZen', 'birth_year': '2012', }; request(app) .post('/actors') .send(actor) .expect(400, done); });
We should expect code 400
(bad request) if we try to create an actor who already exists in the database.
A similar set of tests is present for tests/movies.js
. The function and outcome of each test should be evident now.