In Lesson 5, we implemented a node REST server to perform CRUD operations for device data. We have not yet hooked our server to mongodb, we will do that once we completely understand and unit test existing code base. This is all about Testing, Testing & more Testing. Client side testing using Karma & Server Side Node.js REST API testing using Mocha
Our existing unit test cases handle static data being returned by the services. Since we have refactored our code to use $resource & $http, our unit test cases need to be refactored to utilize _$httpBackend_ which is a mock API provided by angularjs. Understanding $httpBackend takes a while and getting used to. The general steps in testing controllers using httpBackend are:
1. Inject _$httpBackend_
2. Use expectGet, expectPOST & expectPUT & respond API to implement mock backend behavior
3. Run methods under test & verify results
4. _$httpBackend_.flush() to flush pending requests on demand
it('listDeviceController check for devices in the model',
inject(function(_$httpBackend_, $rootScope, $controller, Devices) {
var scope = $rootScope.$new();
var mockBackend = _$httpBackend_;
mockBackend.expectGET('http://localhost:3000/devices').
respond([{id:0, name: "iphone", assetTag:"a23456", owner:"dev", desc:"iOS4.2"}]);
Our existing unit test cases handle static data being returned by the services. Since we have refactored our code to use $resource & $http, our unit test cases need to be refactored to utilize _$httpBackend_ which is a mock API provided by angularjs. Understanding $httpBackend takes a while and getting used to. The general steps in testing controllers using httpBackend are:
1. Inject _$httpBackend_
2. Use expectGet, expectPOST & expectPUT & respond API to implement mock backend behavior
3. Run methods under test & verify results
4. _$httpBackend_.flush() to flush pending requests on demand
Testing Controllers
Injecting _$httpBackend_ into the tests:
it('listDeviceController check for devices in the model',
inject(function(_$httpBackend_, $rootScope, $controller, Devices) {
var scope = $rootScope.$new();
var mockBackend = _$httpBackend_;
Implementing Mock Backend behavior:
mockBackend.expectGET('http://localhost:3000/devices').
respond([{id:0, name: "iphone", assetTag:"a23456", owner:"dev", desc:"iOS4.2"}]);
expectGET specifies the request expectation & respond implements the mock response
Run methods under test
controllerSpec.js: var ctrl = $controller('deviceListController', {$scope: scope}, Devices);
This creates the deviceListController. In the controller the following statement "$scope.devices = Devices.query()" does a query using the Devices service. Essentially, it makes a http GET request. This is intercepted by the ngMock angularjs module & its response is provided to the controller.
Flush the response manually
mockBackend.flush();
Test the conditions
expect(scope.devices).toEqualData([{id:0, name: "iphone", assetTag:"a23456", owner:"dev", desc:"iOS4.2"}]);
Here is the code for controller CRUD testing with ngMock httpBackend:
--
--
Testing Services
Testing services is fairly straightforward. We are mainly ensuring that the services send the right http requests to the backend & expect the right responses.
--
--
Trouble-shooting:
Error: Unknown provider: $resourceProvider <- devices="" div="" resource="">->
ngResource is not part of angularjs core. So, you have to include it specifically in your test specs, like so:
describe('service', function() {
beforeEach(function(){
module('cotd.services');
module('ngResource');
});
Testing the server - Node REST service testing using Mocha, Chai & supertest
Now that we have extensively implemented client side unit testing specs, the focus naturally turns to testing the server side. After reviewing a bunch of server side testing frameworks, i think Mocha provides for a better suite since it allows for BDD a.k.a jasmine style syntax. This allows us to keep the testing constructs fairly consistent between client & server. Lets implement hands on mocha testing for our server.
We can accomplish Server side REST based testing easily by combining 3 frameworks Mocha, Chai & SuperTest. They play well nicely and makes it easier to write Node Based REST tests.
We can accomplish Server side REST based testing easily by combining 3 frameworks Mocha, Chai & SuperTest. They play well nicely and makes it easier to write Node Based REST tests.
Step 1: is to add mocha, Chai & supertest to package.json
{
"name": "cotd-server",
"description": "cotd backend",
"version": "0.0.1",
"private": "true",
"dependencies": {
"express": "3.2.2",
"cors": "*"
},
"devDependencies":{
"mocha": "*",
"chai": "*",
"supertest": "*"
},
"scripts":{
"test": "mocha"
}
}
"name": "cotd-server",
"description": "cotd backend",
"version": "0.0.1",
"private": "true",
"dependencies": {
"express": "3.2.2",
"cors": "*"
},
"devDependencies":{
"mocha": "*",
"chai": "*",
"supertest": "*"
},
"scripts":{
"test": "mocha"
}
}
Step 2: export the express app as a module in server.js. This is for Mocha to test the server functionality without running the server first.
module.exports = app;
module.exports = app;
Step 3: create a test directory in your server folder and Create a new file for the test spec called test-devicesRest.js. Require mocha, chai & supertest in your test spec.
var chai = require('chai'),
express = require('express'),
request = require('supertest');
var app = require('../server');
var expect = chai.expect;
Step 4: writing a GET test
describe('GET /devices', function(){
it('should return 200 and JSON with valid keys', function(done){
request(app)
.get('/devices')
.end(function(err, res){
//validate the keys in the response JSON matches, we dont care about the values
expect(res.status).to.equal(200);
expect(res.body[0]).to.have.keys(['id', 'name', 'assetTag', 'owner', 'desc']);
done();
});
});
});
Step 5: creating a simple Makefile to run our test using Mocha. running make test on the command line should run mocha. It looks for the test directory and executes test specs within it.
REPORTER = list
#REPORTER = dot
test:
@./node_modules/.bin/mocha \
--reporter $(REPORTER)
test-w:
@./node_modules/.bin/mocha \
--reporter $(REPORTER) \
--growl \
--watch
.PHONY: test
Step 6: Write other tests. Here is a complete source code for our REST tests
--
--
var chai = require('chai'),
express = require('express'),
request = require('supertest');
var app = require('../server');
var expect = chai.expect;
Step 4: writing a GET test
describe('GET /devices', function(){
it('should return 200 and JSON with valid keys', function(done){
request(app)
.get('/devices')
.end(function(err, res){
//validate the keys in the response JSON matches, we dont care about the values
expect(res.status).to.equal(200);
expect(res.body[0]).to.have.keys(['id', 'name', 'assetTag', 'owner', 'desc']);
done();
});
});
});
Step 5: creating a simple Makefile to run our test using Mocha. running make test on the command line should run mocha. It looks for the test directory and executes test specs within it.
REPORTER = list
#REPORTER = dot
test:
@./node_modules/.bin/mocha \
--reporter $(REPORTER)
test-w:
@./node_modules/.bin/mocha \
--reporter $(REPORTER) \
--growl \
--watch
.PHONY: test
Step 6: Write other tests. Here is a complete source code for our REST tests
--
--
git tag
git checkout v1.6