I'm using the "express-validator" middleware package to validate some parameters for this exampleController endpoint. What would be the best way to stub out this controller for unit tests? I keep getting errors like:
TypeError: errors.isEmpty is not a function
router
var controller = require('./controllers/exampleController.js');
var express = require('express');
var router = express.Router();
router.get('/example', controller.exampleController);
exampleController.js
exports.doSomething = function(req, res, next) {
var schema = {
'email': {
in: 'query',
isEmail: {
errorMessage: 'Invalid Email'
}
},
'authorization': {
in: 'headers',
// custom test
isValidAuthToken: {
errorMessage: 'Missing or malformed Bearer token'
}
}
};
// Validate headers/query params
req.check(schema);
// Handle response
req.getValidationResult()
.then(function(errors) {
if (!errors.isEmpty()) {
return res.status(400).json({ error: 'Bad Request' });
} else {
var context = {
email: req.query.email,
};
return res.render('index', context);
}
})
};
test
var chai = require('chai');
var sinonChai = require('sinon-chai');
chai.Should();
chai.use(sinonChai);
global.sinon = require('sinon');
var sinonStubPromise = require('sinon-stub-promise');
sinonStubPromise(sinon);
var rewire = require('rewire');
var exampleController = rewire('../controllers/exampleController.js');
var errorsResponse = [{
param: 'email',
msg: 'Invalid Email',
value: undefined
}];
describe('exampleController', function() {
var req;
var res;
beforeEach(function() {
req = {
headers: {},
query: {},
check: sinon.spy(),
getValidationResult: sinon.stub().returnsPromise()
};
res = {
status: sinon.stub().returns({
json: json
}),
render: sinon.spy()
};
});
afterEach(function() {
req.query = {};
});
context('when missing email query param', function() {
beforeEach(function() {
req.getValidationResult.resolves(errorsResponse);
exampleController.doSomething(req, res);
});
it('should call status on the response object with a 400 status code', function() {
res.status.should.be.calledWith(400);
});
it('should call json on the status object with the error', function() {
json.should.be.calledWith({ error: 'Bad Request' });
});
});
});
});
The way you have structured the unit test for validating a controller is not really consistent. I will try to present you the issues and workarounds in detail, but before we move on have a look at this great article on unit testing Express controllers.
Ok, so regarding the initial error you presented TypeError: errors.isEmpty is not a function that was due to a malformed response object you had setup for stubbing the getValidationResult() method.
After printing out a sample response object from this method you will notice that the correct structure is this:
{ isEmpty: [Function: isEmpty],
array: [Function: allErrors],
mapped: [Function: mappedErrors],
useFirstErrorOnly: [Function: useFirstErrorOnly],
throw: [Function: throwError] }
instead of your version of the response:
var errorsResponse = [{
param: 'email',
msg: 'Invalid Email',
value: undefined
}];
isEmpty() is a top-level function and you should have used an array attribute for storing the errors list.
I'm attaching a revamped version of your controller and test scenario so that you can correlate it with the best practices presented in the aforementioned article.
controller.js
var express = require('express');
var router = express.Router();
router.get('/example', function(req, res) {
var schema = {
'email': {in: 'query',
isEmail: {
errorMessage: 'Invalid Email'
}
}
};
// Validate headers/query params
req.check(schema);
// Handle response
req.getValidationResult()
.then(function(errors) {
if (!errors.isEmpty()) {
return res.status(400).json({
error: 'Bad Request'
});
} else {
var context = {
email: req.query.email,
};
return res.render('index', context);
}
});
});
module.exports = router;
test.js
'use strict';
const chai = require('chai');
const sinon = require('sinon');
const SinonChai = require('sinon-chai');
var sinonStubPromise = require('sinon-stub-promise');
sinonStubPromise(sinon);
chai.use(SinonChai);
chai.should();
var mockHttp = require('node-mocks-http');
var controller = require('./controller.js');
describe.only('exampleController', function() {
context('when missing email query param', function() {
var req;
var res;
beforeEach(function() {
// mock the response object
// and attach an event emitter
// in order to be able to
// handle events
res = mockHttp.createResponse({
eventEmitter: require('events').EventEmitter
});
});
it('should call status on the response object with a 400 status code',
(done) => {
// Mocking req and res with node-mocks-http
req = mockHttp.createRequest({
method: 'GET',
url: '/example'
});
req.check = sinon.spy();
var errorsResponse = {
isEmpty: function() {
return false;
},
array: [{
param: 'email',
msg: 'Invalid Email',
value: undefined
}]
};
// stub the getValidationResult()
// method provided by the 'express-validator'
// module
req.getValidationResult = sinon.stub().resolves(errorsResponse);
// spy on the response status
sinon.spy(res, 'status');
sinon.spy(res, 'json');
// called when response
// has been completed
res.on('end', function() {
try {
// assert status and JSON args
res.status.should.have.been.calledWith(400);
res.json.should.have.been.calledWith({error: 'Bad Request'});
done();
} catch (e) {
done(e);
}
});
// Call the handler.
controller.handle(req, res);
});
});
});
A few points to notice in the updated version of the test.
Instead of manually constructing request / response objects, you should better use a library that's already there for this job. In my version I'm using 'node-mocks-http' which is pretty much a standard when it comes to Express.
When testing controllers, instead of manually calling the service method it's better to use the natural routing mechanism through the mocked HTTP request object. This way you can cover both happy & sad routing paths
Using a common HTTP req / res mocking library, means less work for you - all you need to do is extend the factory objects with non-standard functions (e.g. getValidationResult() from express-validator) and add your spies / stubs seamlessly
Finally, the library supports attaching event listeners on response events that otherwise you could not simulate manually. In this example, we're listening for the end event from the response object that is called after the return res.status(400).json({error: 'Bad Request'}); method has been called in your controller.
Hope I've cleared things up a bit :)
Related
I'm using Ember-simple-auth and trying to return data from my custom authenticator back into the controller that did the authenticating.
in my authenticator/custom.js:
authenticate(identification, password) {
return new Ember.RSVP.Promise(function (resolve, reject) {
var loginURL = 'https://domain.com/login';
var authObj = Ember.Object.create({"identity":identification,"password":password});
var hash = authObj.getProperties('identity', 'password');
var stringHash = JSON.stringify(hash);
var xhr = new XMLHttpRequest();
xhr.open("post", loginURL);
xhr.onreadystatechange = handler;
xhr.responseType = 'json';
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Format', 'JSON');
xhr.send(stringHash);
function handler() {
if (this.readyState === this.DONE) {
if (this.status === 200) {
console.log('response is: ',this.response); /// <-- returns good response data
resolve(this.response);
} else {
reject( 'failed with status: [' + this.status + ']');
}
}
}
and in my login controller:
authenticate() {
let { identification, password } = this.getProperties('identification', 'password');
var session = this.get('session');
session.authenticate('authenticator:custom', identification, password).then((reason) => {
console.log('failed login',reason);
});
}
},
But I'd really like to be able to handle the resolve function and get it's value payload from the authenticate promise.
If I change the .catch to a .then the response function is successfully called but always has an undefined value as its payload:
session.authenticate('authenticator:custom', identification, password).then(
(response) => {
console.log('response: ',response); //// <---- always 'undefined'
this.setUserWithData(response);
},
(reason) => {
this.set('Login failed: ',reason);
}
);
}
Even if I restructure the promise, rearrange how the function is called, the first function from an RSVP is successfully called, but has an undefined payload. The second function from an RSVP always has a correct payload.
I tried reversing the resolve/reject:
Ember.RSVP.Promise(function (resolve, reject){
to
Ember.RSVP.Promise(function (reject, resolve){
and the function successfully carries the response, but the simple-auth now believes it has failed its authorization.
I'd like to be able to pass the response payload into my controller. Or, if that can't be done, how can I inject data from the response into my session and ember-data store? It didn't seem like good practice to call and insert data into the store from within the authenticate function of the authenticator.
The session's authenticate method doesn't resolve with a value. Check the API docs: http://ember-simple-auth.com/api/classes/SessionService.html#method_authenticate
In order to deal with the response from the authentication route, I used the function sessionAuthenticated to deal with the returned data.
So, the authenticate function in authenticators/custom.js
authenticate(identification, password) {
return new Ember.RSVP.Promise(function (resolve, reject) {
var loginURL = 'https://domain.com/login';
var authObj = Ember.Object.create({"identity":identification,"password":password});
var hash = authObj.getProperties('identity', 'password');
var stringHash = JSON.stringify(hash);
var xhr = new XMLHttpRequest();
xhr.open("post", loginURL);
xhr.onreadystatechange = handler;
xhr.responseType = 'json';
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Format', 'JSON');
xhr.send(stringHash);
function handler() {
if (this.readyState === this.DONE) {
if (this.status === 200) {
console.log('200 response: \r',this.response);
resolve(this.response);
} else {
console.log('failure response',this.response);
reject(this.response);
}
}
}
});
},
With the sessionAuthenticated() event taking place in routes/application.js:
sessionAuthenticated: function(){
var session = this.get('session');
var data = session.get('data');
var response = data.authenticated;
console.log('sessionAuthenticated()',response);
},
sessionInvalidated: function(){
console.log('sessionInvalidated()',response);
},
From the sessionAuthenticated function, the response variable contains all the information passed to it from authenticate(response) inside the authenticator.
I have the following factory that I'd like to test:
angular
.module('enigma.authFactory', [])
.factory('authFactory', authFactory);
authFactory.$inject = ['$http'];
function authFactory($http){
function doesUserExist(email){
return $http.post('/doesUserExist', email)
.success(function(data){
if(data !== 'user exists'){
return false;
} else {
return true;
}
});
}
}
So I wrote the following test:
describe('Auth Service Tests', function() {
var $httpBackend, defer, doesUserExistReqHandler;
beforeEach(inject(function(_$httpBackend_, $injector, $q) {
$httpBackend = _$httpBackend_;
defer = $q.defer();
doesUserExistReqHandler = $httpBackend.when('POST', '/doesUserExist').respond(defer.promise);
}));
describe('authFactory.doesUserExist()', function() {
it('should return true is a user exists', function() {
user = {
email: 'bwayne#wayneenterprise.com'
};
$httpBackend.whenPOST('/doesUserExist', user).respond('user exists');
var doesUserExist = authFactory.doesUserExist(user);
$httpBackend.flush();
expect(doesUserExist).toEqual(true);
});
});
});
I checked inside the authFactory.doesUserExist function and I am correctly getting the data set to 'user exists' which routes the function to return true. However in the unit test authFactory.doesUserExist is being set to the following object.
Expected Object({ $$state: Object({ status: 1, pending: undefined, value: Object({ data: Object({ $$state: Object({ status: 0 }) }), status: 200, headers: Function, config: Object({ method: 'POST', transformRequest: [ Function ], transformResponse: [ Function ], paramSerializer: Function, url: '/doesUserExist', data: Object({ email: 'bwayne#wayneenterprise.com' }), headers: Object({ Accept: 'application/json, text/plain, */*', Content-Type: 'application/json;charset=utf-8' }) }), statusText: '' }), processScheduled: false }), success: Function, error: Function }) to equal true.
I'm thinking the issue is that the test isn't resolving the promise properly and so I'm setting the res variable before authFactory.doesUserExist has returned true.
How would I go about fixing this?
So a few things needs to happen to get your code to work with what you have.
Here is a demo http://plnkr.co/edit/4GvMbPJgc0HcJcgFZ4DL?p=preview
Your service (factory) needs to return an object.
You are not returning a promise after your $http post.
I recommend you use the $q service.
In the testing
You need to import your module.
Be sure to inject your service
You should remove $httpBackend.when('POST', '/doesUserExist').respond(defer.promise); since it is not accomplishing anything and it is actually getting it confused in other $httpBackend.whenPost.
You should be asserting the response data instead of the promise, because authFactory.doesUserExist(user) returns a promise.
Code:
var doesUserExist = authFactory.doesUserExist(user)
.then(function (data) {
responseData = data;
});
$httpBackend.flush();
expect(responseData).toEqual(true);
Hi I am new to unit testing. I am currently working on Mocha,Chai and Sequelize to make a TDD unit test for an API. But while running the unit test using Mocha, I get the following error:
msg: 'TypeError: Cannot read property \'firstName\' of undefined' } to not exist
The unit test I have written for my API is as follows:
describe('createUser', function () {
it('should create a User', function (done) {
var email_Id = "xyz#gmail.com";
var firstName = "xyx";
var values = JSON.stringify({
body: {
email_Id: email_Id,
firstName: firstName
}
});
user.createUser(values, function (err, response) {
expect(response).should.be.an(object);
expect(err).to.be.null;
});
done();
});
});
My API is as follows:
createUser: function (req, res) {
var param = req.body;
var firstName = param.firstName;
var email_Id = param.email_Id;
db.sequelize.sync().then(function () {
user.create(param).then(function (response) {
// return a Success Response in JSON format
}).catch(function (err) {
// return an Error Response in JSON format
});
});
}
}
Can Somebody help me as to why am I getting this error?
I'm setting up unit tests on my Sails application's models, controllers and services.
I stumbled upon a confusing issue, while testing my User model. Excerpt of User.js:
module.exports = {
attributes: {
username: {
type: 'string',
required: true
},
[... other attributes...] ,
isAdmin: {
type: 'boolean',
defaultsTo: false
},
toJSON: function() {
var obj = this.toObject();
// Don't send back the isAdmin attribute
delete obj.isAdmin;
delete obj.updatedAt;
return obj;
}
}
}
Following is my test.js, meant to be run with mocha. Note that I turned on the pluralize flag in blueprints config. Also, I use sails-ember-blueprints, in order to have Ember Data-compliant blueprints. So my request has to look like {user: {...}}.
// Require app factory
var Sails = require('sails/lib/app');
var assert = require('assert');
var request = require('supertest');
// Instantiate the Sails app instance we'll be using
var app = Sails();
var User;
before(function(done) {
// Lift Sails and store the app reference
app.lift({
globals: true,
// load almost everything but policies
loadHooks: ['moduleloader', 'userconfig', 'orm', 'http', 'controllers', 'services', 'request', 'responses', 'blueprints'],
}, function() {
User = app.models.user;
console.log('Sails lifted!');
done();
});
});
// After Function
after(function(done) {
app.lower(done);
});
describe.only('User', function() {
describe('.update()', function() {
it('should modify isAdmin attribute', function (done) {
User.findOneByUsername('skippy').exec(function(err, user) {
if(err) throw new Error('User not found');
user.isAdmin = false;
request(app.hooks.http.app)
.put('/users/' + user.id)
.send({user:user})
.expect(200)
.expect('Content-Type', /json/)
.end(function() {
User.findOneByUsername('skippy').exec(function(err, user) {
assert.equal(user.isAdmin, false);
done();
});
});
});
});
});
});
Before I set up a policy that will prevent write access on User.isAdmin, I expect my user.isAdmin attribute to be updated by this request.
Before running the test, my user's isAdmin flag is set to true. Running the test shows the flag isn't updated:
1) User .update() should modify isAdmin attribute:
Uncaught AssertionError: true == false
This is even more puzzling since the following QUnit test, run on client side, does update the isAdmin attribute, though it cannot tell if it was updated, since I remove isAdmin from the payload in User.toJSON().
var user;
module( "user", {
setup: function( assert ) {
stop(2000);
// Authenticate with user skippy
$.post('/auth/local', {identifier: 'skippy', password: 'Guru-Meditation!!'}, function (data) {
user = data.user;
}).always(QUnit.start);
}
, teardown: function( assert ) {
$.get('/logout', function(data) {
});
}
});
asyncTest("PUT /users with isAdmin attribute should modify it in the db and return the user", function () {
stop(1000);
user.isAdmin = true;
$.ajax({
url: '/users/' + user.id,
type: 'put',
data: {user: user},
success: function (data) {
console.log(data);
// I can not test isAdmin value here
equal(data.user.firstName, user.firstName, "first name should not be modified");
start();
},
error: function (reason) {
equal(typeof reason, 'object', 'reason for failure should be an object');
start();
}
});
});
In the mongoDB console:
> db.user.find({username: 'skippy'});
{ "_id" : ObjectId("541d9b451043c7f1d1fd565a"), "isAdmin" : false, ..., "username" : "skippy" }
Yet even more puzzling, is that commenting out delete obj.isAdmin in User.toJSON() makes the mocha test pass!
So, I wonder:
Is the toJSON() method on Waterline models only used for output filtering? Or does it have an effect on write operations such as update().
Might this issue be related to supertest? Since the jQuery.ajax() in my QUnit test does modify the isAdmin flag, it is quite strange that the supertest request does not.
Any suggestion really appreciated.
I am trying to create some basic test coverage of a service that I have created. Here is my service:
App.factory('encounterService', function ($resource, $rootScope) {
return {
encounters: [],
encountersTotalCount: 0,
encountersIndex: 0,
resource: $resource('/encounters/:encounterId', {encounterId:'#encounterId'}, {
search: {
method: 'GET',
headers: {
'RemoteUser': 'jjjyyy',
'Content-Type': 'application/json'
}
}
}),
getMoreEncounters: function() {
var that = this;
that.resource.search({}, function(data) {
that.encountersTotalCount = data.metadata.totalCount;
_.each(data.encounters, function(encounter) {
that.encounters.push(encounter);
});
that.busy = false;
that.offset += 10;
$rootScope.$broadcast('encountersFetched');
});
}
};
});
Here is my test that I cannot get to work:
describe('encounterService', function() {
var _encounterService, httpBackend;
beforeEach(inject(function(encounterService, $httpBackend) {
_encounterService = encounterService;
httpBackend = $httpBackend;
var url = '/encounters/';
httpBackend.when('GET', url).respond([{}, {}, {}]);
}));
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
it('should return a list of encounters', function() {
_encounterService.getMoreEncounters();
httpBackend.flush();
expect(_encounterService.encounters.size).toBe(3);
});
});
The error I get is
Chrome 31.0.1650 (Mac OS X 10.8.5) Clinical App services encounterService should return a list of encounters FAILED
Error: Unexpected request: GET encounters
No more request expected
at $httpBackend (/Users/mhamm/Developer/clinical/app/bower_components/angular-mocks/angular-mocks.js:1179:9)
at sendReq (/Users/mhamm/Developer/clinical/app/bower_components/angular/angular.js:7611:9)
at $http.serverRequest (/Users/mhamm/Developer/clinical/app/bower_components/angular/angular.js:7345:16)
at wrappedCallback (/Users/mhamm/Developer/clinical/app/bower_components/angular/angular.js:10549:81)
at wrappedCallback (/Users/mhamm/Developer/clinical/app/bower_components/angular/angular.js:10549:81)
at /Users/mhamm/Developer/clinical/app/bower_components/angular/angular.js:10635:26
at Scope.$eval (/Users/mhamm/Developer/clinical/app/bower_components/angular/angular.js:11528:28)
at Scope.$digest (/Users/mhamm/Developer/clinical/app/bower_components/angular/angular.js:11373:31)
at Function.$httpBackend.flush (/Users/mhamm/Developer/clinical/app/bower_components/angular-mocks/angular-mocks.js:1453:16)
at null.<anonymous> (/Users/mhamm/Developer/clinical/test/spec/clinical.spec.js:78:21)
Error: [$rootScope:inprog] $digest already in progress
http://errors.angularjs.org/1.2.0/$rootScope/inprog?p0=%24digest
at /Users/mhamm/Developer/clinical/app/bower_components/angular/angular.js:78:12
at beginPhase (/Users/mhamm/Developer/clinical/app/bower_components/angular/angular.js:11830:15)
at Scope.$digest (/Users/mhamm/Developer/clinical/app/bower_components/angular/angular.js:11364:9)
at Function.$httpBackend.verifyNoOutstandingExpectation (/Users/mhamm/Developer/clinical/app/bower_components/angular-mocks/angular-mocks.js:1486:16)
at null.<anonymous> (/Users/mhamm/Developer/clinical/test/spec/clinical.spec.js:68:21)
I do not fully understand mocking, so I am sure I am doing something basic incorrectly. Please show me what I am doing wrong.
$resource automatically removes the trailing slashes from the url.
From version 1.3.0 there is a fourth argument that allows you to set stripTrailingSlashes: false to keep those.