Lab 6: Testing in Sails and Node.js

Test Cases

In this lab, we would covers several test cases for the ls05 system. Here are some examples in table format. Usually in the documentation, test cases are shown in the following format.

Test# Description App version Expected Result Actual Result
1 User (model) initial data should have an user with username “admin” 0.0.0 Should return one user  
2 User (model) initial data should have an user with username is not “admin” 0.0.0 Should return one user  
3 UserController - #login() with the admin account 0.0.0 should return 200 with “Login successfully”  

Mocha: A testing framework for Node.js application

Mocha is a feature-rich JavaScript test framework running on Node.js and in the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test cases. Hosted on GitHub.

Configuration

Please clone your ls05 Sails project from Github.

git clone https://your_project_git/your_ls05.git

In the project directory, we run npm install mocha --save-dev to add mocha library in development dependencies.

cd your_project_location
npm install mocha --save-dev

--save-dev means that this module would only be added to development environment. It would not available in production environment.

Mocha config in package.json

We need to declare the mocha config object in package.json. Please put the followings before the ending }.

"mocha": {
  "diff": true,
  "extension": ["js"],
  "package": "./package.json",
  "reporter": "spec",
  "slow": 75,
  "timeout": 10000,
  "ui": "bdd",
  "watch-files": ["lib/**/*.js", "test/**/*.js"],
  "watch-ignore": ["lib/vendor"]
}

test/ directory structure

We would like to put all testing related stuffs in test folder in our project. I have created the folder skeleton for you. Please download test.zip from BUMoodle and extract in your sails project . After that, your sails project file structure would look similar to the followings.

./myApp
├── api/
├── assets/
├── config/
├── ...
├── test/
│  ├── integration/
│  │  ├── controllers/
│  │  │  └── ...
│  │  ├── models/
│  │     └── ...
│  ├── fixtures/
|  │  └── ...
│  └── lifecycle.test.js
├── ...
├── views/
├── ...
└── package.json

Example 1: Unit test for testing initial data of User Models

Let’s develop a unit test function for User model in our Sails app.

Unit test functions for models

Please create a new file test/integration/models/User.test.js with the following codes. In this unit test file, we have 2 test cases, which are testing the initial data inserted in config/bootstrap.js.


describe('User (model) initial data', function() {

  describe('Admin user', function() {
    it('should find an User with username "admin"', function (done) {
      User.find({username:'admin'})
      .then(function(users) {

        if (users.length == 0) {
          return done(new Error(
            'Should return 1 user -- the admin '+
            'But instead, got: no user found'
          ));
        }
        return done();
      })
      .catch(done);
    });
  });

  describe('other user', function() {
    it('should find an User with username not equal to "admin"', function (done) {
      User.find({username:{'!=':'admin'}})
      .then(function(users) {

        if (users.length == 0) {
          return done(new Error(
            'Should return at least 1 user '+
            'But instead, got: no user found'
          ));
        }
        return done();
      })
      .catch(done);
    });
  });

});

Run the tests

To run all tests, please issue the following command in terminal.

node ./node_modules/mocha/bin/mocha test/lifecycle.test.js test/integration/**/*.test.js

We would like to simplify the previous command. Please modify package.json, changing it to run the unit tests with npm test.

Please REPLACE this line in "script": { ... }.

"test": "npm run lint && npm run custom-tests && echo 'Done.'",

WITH the following line.

"test": "node ./node_modules/mocha/bin/mocha test/lifecycle.test.js test/integration/**/*.test.js",

After that, we could simply run the tests by npm test in terminal.

npm test

or even simpler

npm t

Supertest: for testing HTTP request and response

Supertest is a Javascript module to provide a high-level abstraction for testing HTTP. We could easy to make a HTTP request to our Sails app and check the result for testing. It works great with Mocha.

There are several examples on its official Github, please have a look.

Configuration

npm install supertest --save-dev

Example 2: Testing controller actions

Writing Unit test for login and logout action

Please create a new Javascript file at test/integration/controllers/UserController.test.js with the following codes.

const supertest = require('supertest');

describe('UserController', function() {

  describe('#login() with admin account', function() {
    it('should return status 200 with "Login successfully" in body', function (done) {
      supertest(sails.hooks.http.app)
      .post('/user/login')
      // The following two lines making the request as normal form submission instead of AJAX request
      .set('Accept', 'text/html,application/xhtml+xml,application/xml')
      .set('Content-Type', 'application/x-www-form-urlencoded')
      .send("username=admin&password=123456")
      .expect(200, '"Login successfully"', done);
    });
  });

  describe('#login() with non-exists user account', function() {
    it('should return status 401 with "User not found" in body', function (done) {
      supertest(sails.hooks.http.app)
      .post('/user/login')
      .send("username=testing&password=123456")
      .expect(401, 'User not found', done);
    });
  });

  describe('#logout()', function() {
    it('should return status 200 with "Log out successfully" in body', function (done) {
      supertest(sails.hooks.http.app)
      .get('/user/logout')
      .expect(200, '"Log out successfully"', done);
    });
  });

});

Then run tests by npm test.

Using async module to implement complex unit test

Configuration

npm install async --save

Example 3: Writing unit test for PersonController.create

Please create test/integration/controllers/PersonController.test.js with the following codes.

const supertest = require('supertest');
const Async = require('async');

describe('PersonController', function() {

  let cookie;


  describe(`Policy: #create() Person[name]=Peter and Person[age]=50 without login`, function() {
    it('should return 403 Forbidden', function (done) {
      supertest(sails.hooks.http.app)
      .post('/person/create')
      .send("Person[name]=Peter&Person[age]=50")
      .expect(403, done);
    });
  });

  describe(`#create() Person[name]=Peter and Person[age]=50 with admin login`, function() {
    it('should return 200 "Successfully created!"', function (done) {
      Async.series([
        function(cb) {
          supertest(sails.hooks.http.app)
          .post('/user/login')
          .send({ username: 'admin', password: '123456' })
          .expect(302)
          .then(res => {
            const cookies = res.headers['set-cookie'][0].split(',').map(item => item.split(';')[0]);
            cookie = cookies.join(';');
            cb();
          });
        },
        function(cb) {
          supertest(sails.hooks.http.app)
          .post('/person/create')
          .set('Cookie', cookie)
          .send("Person[name]=Peter&Person[age]=50")
          .expect(200, '"Successfully created!"', cb);
        }
      ], done);
    });
  });
});

In the above example, we use Async.series([...]); to ensure the execution sequence of those async functions. Each async function would execute one-by-one in the array.

Ensure execution order of unit test functions

Mocha runs the unit tests in by sorting the files alphabetically. If we would like to ensure the execution order, we need to rename the folders and files by adding digis at beginning of the filename.

./myApp
├── api/
├── assets/
├── config/
├── ...
├── test/
│  ├── integration/
│  │  ├── 01-models/
│  │  │  └── 01-User.test.js
│  │  ├── 02-controllers/
│  │  │  ├── 01-UserController.test.js
│  │  │  └── 02-PersonController.test.js
│  ├── fixtures/
|  │  └── ...
│  └── lifecycle.test.js
├── ...
├── views/
├── ...
└── package.json

In the above file naming, the unit test functions would run in following sequence.

UserModel -> UserController -> PersonController