Lab 6: Testing in Sails and Node.js
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