Introduction #
In the previous post Testing Node applications with Intern framework I demostrated how to set up testing environment for unit tests with Intern and Grunt. Today I will show how to test REST endpoints with end to end tests in a speed of unit tests.
#Set up infrastructure
Create Node project #
Create NodeJS project:
mkdir intern-end-to-end-tests
cd intern-end-to-end-tests
Create init package.json
:
{
"name": "intern-end-to-end-tests",
"version": "1.0.0",
"description": ""
}
Implementing REST endpoint using ExpressJS #
Install ExpressJS: #
npm install express --save-dev
Implement application endpoints #
We will create application code in src
directory:
mkdir src
Create file: src/app.js
:
var express = require('express');
var app = express();
app.get('/test', function (req, res) {
console.log('hitting test');
res.json({status: 'ok'});
});
console.log('App is configured');
module.exports = app;
Above application hosts /test
endpoint which returns JSON: {status: 'ok'}
and logs activity each activity.
Hosting application on the server #
To host that application we will need server.
Create file: src/server.js
:
var app = require('./app');
app.listen(8080);
console.log('Application is running on port 8080');
Lets run server:
node src/server
Console output should display:
App is configured
Application is running on port 8080
hitting test
And when hitting: http://localhost:8080/test
in the browser you should get:
{
"status": "ok"
}
Install and initialize Grunt #
Before we start writing tests we need some test executor. For this purpose I use Grunt.
Installing Grunt dependencies #
If you never used Grunt before you must first install it globally:
npm install -g grunt
Install Grunt locally:
npm install grunt --save-dev
Grunt configuration #
Configure grunt, create Gruntfile.js
:
module.exports = function (grunt) {
grunt.initConfig({
});
grunt.registerTask('default', []);
};
Now if you execute: grunt
you should have such console output:
Done, without errors.
Install and configure Intern #
Installing Intern dependencies #
Install Intern dependency:
npm install intern --save-dev
Intern configuration #
Create tests directory:
mkdir test
Prepare intern configuration by copying example from intern directory:
cp node_modules/intern/tests/example.intern.js test/intern-config.js
Create first test #
mkdir test/e2e
Create file, test/e2e/testRestTest.js
:
define(function (require) {
var suite = require('intern!object');
var expect = require('intern/chai!expect');
suite({
' strings': function () {
expect("Piotr").to.equal('Piotr');
}
});
});
Execute Intern tests with Grunt #
Now we are ready to prepare test executor.
Configure Grunt #
Lets modify Gruntfile.js
:
module.exports = function (grunt) {
grunt.initConfig({
intern: {
e2e_testing: {
options: {
config: 'test/intern-config',
suites: ['test/e2e/testRestTest']
}
}
}
});
grunt.loadNpmTasks('intern');
grunt.registerTask('default', ['test']);
grunt.registerTask('test', ['intern']);
};
This configuration loads intern
task, configures intern:e2e_testing
task and assignes it to test
alias task.
Executing Intern tests #
Lets execute test task:
grunt test
With output:
Running "intern:e2e_testing" (intern) task
>> PASS: main - - strings (0ms)
>> 0/1 tests failed
>> 0/1 tests failed
Summary #
At this point we have configure:
- REST application with
/test
endpoint - Server that hosts that application
- Configured task executor, and tasks configuration
- Configured test framework with unit suites
- Written first unit test which actually doesn’t test application, but was made to check if everything works fine
Pretty much, isn’t it?
Create real end to end test #
At this point we have configured infrastructure that allows now to focus on creating real end to end test. The sequence of the testing is:
- Prepare Express application and host it on the local server
- Execute all suite test cases which will call local REST endpoint and ensure they are valid
- Shut down server
Full example #
Full example of the test lokks as below:
define(function (require) {
var suite = require('intern!object'),
app = require('intern/dojo/node!../../src/app'),
chai = require('intern/dojo/node!chai'), //dellivers promieses implementation used in setup method
expect = chai.expect,
chaiHttp = require('intern/dojo/node!chai-http'), //dellivers promieses implementation used in setup method
q = require('intern/dojo/node!q'), //dellivers promieses implementation used in setup method
portfinder = require('intern/dojo/node!portfinder'); //dellivers promieses implementation used in setup method
var me = this;
var api = function () {
return chai.request('http://localhost:' + me.port);
}
suite({
'setup': function () {
chai.use(chaiHttp); //configure chai with chaiHttp
chai.request.addPromises(q.Promise); //configure promise for chaiHttp
var deferred = q.defer();
portfinder.getPort(function (err, port) {
me.port = port;
me.server = app.listen(me.port, function () {
console.log('server is running on port %j', me.port);
deferred.resolve();
});
});
return deferred.promise;
},
'teardown': function () {
me.server.close();
},
'shoud test return status ok json': function () {
return api() //prepare url
.get('/test') //make a call
.then(function (res) { //return call result
expect(res.body).to.eql({status: 'ok'}); //make assertions with chai
});
},
'another test': function () {
return api()
.get('/test')
.then(function (res) {
expect(res.body).to.eql({status: 'ok'});
});
}
});
});
and it requires following dependencies:
npm install chai chai-http q portfinder --save-dev
Explanation #
Dependencies #
- chai - is an assertion library that allows to ensure some preconditions, like
request body must equals json: {status: 'ok'}
- chai-http - is a plugin for chai that allows to construct and handles HTTP calls, for example making GET call to http://localhost:8080/test
- q - is a library that allows working with promises in NodeJS application, which allows to work with asynhronous tests
- portfinder - is a simple tool to find an open port or domain socket on the current machine
Lifecycle #
When Intern tests are executed, the test system follows a specific lifecycle:
For each registered root suite:
- The
setup
method of the suite is called, if it exists - For each test within the suite:
- The
beforeEach
method of the suite is called, if it exists * The test function is called - The
afterEach
method of the suite is called, if it exists
- The
- The
teardown
method of the suite is called, if it exists
In order to start server once for all tests and close it on the end of the suite we will start it in setup
method and shut it down in teardown
.
Source: https://theintern.github.io/intern/#test-lifecycle
Asynchronous tests #
Most of the testing will be asynchronous, for example all HTTP calls are asynchronous, server will start in asynchronous and also finding free port is asynchronous. Intern support two mechanisms of handling asynchronous tests:
- Using
this.async()
- Promises
First method is very simple, but doesn’t work in lifecycle methods, and looks disgusting. This way is documented very well in the Intern guide so I won’t go this way. The benefits with promises are:
- They are supported in tests and also in lifecycle methods. If simple tests doesn’t require to handle asynchronous methods in for example
setup
method you don’t need go with promises. As we will start server we need to stick with promises. - They looks much more better, and I mean they are more easy to write so also maintain.
- Soon they will become standard
- Easy to use in Intern test
Promise is not supported out of the box, and we need to deliver it with one of many existing libraries. In ths case we will use q
library.
Promise interface from q
needs to be registred with chai-http
in order to support then
method which returns promise.
To make test to be asynchronous with promise, the only thing to do is to return promise. This is why we return .then
in the test.
Source: https://theintern.github.io/intern/#async-tests
Improvements #
Intern allows to execute suites simultaneously. If we start many end to end suites it might turn out that we are using same port. To detect free port and use it I use portfinder
library.
Summary #
In this post I demonstrated how to set up testing environment for NodeJS application using Intern framework and Grunt task runner. Then I’ve modified simple unit tests to run local server with endpoints which needs to be tested. After that we are able to call them and assure that responses are correct. Please notice that the speed of set up the whole server doesn’t differ so much from unit tests.
Source code: https://github.com/kospiotr/hello-js/tree/master/intern-end-to-end-tests