How do I unit test a function that fires an Ajax call in jasmine - unit-testing

For example something like this
func do something()
{
api.ajax('') //Ajax call that triggers inside the function
.done(response){
do something with response
}
}
How do I get to expect something from the response and make my assertions? ANy help would be really helpful! Thanks in advance!

Using jasmine-ajax in tests looks like this:
describe('Sample test', function(){
var request = function(arg){
return new Promise(function(resolve, reject){
$.ajax({
method:'GET',
url: 'example.com/api/'+arg,
success: function(response){
resolve(changeResponse(response));
},
error: reject
});
});
function changeResponse(response){
return response.key + '-changed';
}
};
var TestResponses = {
someEndPoint: {
success: {
status: 200,
responseText: '{"key":"value"}'
}
}
};
beforeEach(function(){
jasmine.Ajax.install();
});
afterEach(function(){
jasmine.Ajax.uninstall();
});
it('can be called with for some end-point', function(){
spyOn($, 'ajax');
request('something');
expect($.ajax).toHaveBeenCalled();
var actualAjaxOptions = $.ajax.calls.mostRecent().args[0];
expect(actualAjaxOptions).toEqual(jasmine.objectContaining({
method:'GET',
url: 'example.com/api/something',
}));
});
it('modifies response as expected', function(done){
request('something').then(function(){
expect(arguments[0]).toEqual('value-changed');
done();
}, function(reason){
done.fail();
});
jasmine.Ajax.requests.mostRecent().respondWith(TestResponses.someEndPoint.success);
});
})
Pleas note that:
jasmine-ajax is currently compatible with any library that uses
XMLHttpRequest. Tested with jQuery and Zepto.
(from docs)

Related

Unit test node controller/promises using express-validator

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 :)

karma mock promise response

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);

Trying to mock $http in Angular

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.

Ajax without ember data - Uncaught TypeError: Object #<Object> has no method 'forEach'

I'm attempting to build a non blocking async call in an Ember.js app without using Ember Data.
I have the following Ember.js model:
App.Player = Ember.Object.extend({
id: '',
alias: '',
name: '',
twitterUserName: '',
isFeatured: ''
});
App.Player.reopenClass({
getPlayers: function () {
var players = Ember.ArrayProxy.create({ content: [] });
$.getJSON("/api/players").then(function (response) {
response.forEach(function (p) {
players.pushObject(App.Player.create(p));
});
});
return players;
}
});
And I am calling it as follows in my route:
App.IndexRoute = Ember.Route.extend({
model: function (params) {
return App.Player.getPlayers();
}
});
For some reason I am getting the following javascript error:
Uncaught TypeError: Object # has no method 'forEach'
I've tried a few variants I have seen around but nothing seems to work. Any help would be appreciated...
EDIT - Found the solution with some help from Darshan, here's the working code:
App.Player.reopenClass({
getPlayers: function () {
var players = [];
$.ajax({
url: "/api/players",
}).then(function (response) {
response.players.forEach(function (player) {
var model = App.Player.create(player);
players.addObject(model);
});
});
return players;
}
});
Your response.forEach suggests that you are expecting the json response body to be an array. It is probably wrapped in some root element like players or data like so.
{
"players": [...]
}
If that is the case you need to use forEach on that root element like response.players.forEach.
You also want to restructure that code to return a promise directly. The Ember router will then pause until your json is loaded and only proceed after it finishes. Something like this,
getPlayers: function () {
return $.getJSON("/api/players").then(function (response) {
var players = Ember.ArrayProxy.create({ content: [] });
response.players.forEach(function (p) {
players.pushObject(App.Player.create(p));
});
return players;
});
}
Returning players resolve the promise. And Ember understands that when a promise resolves that result is the model.

How do I stub a response for an Angular $resource that depends on an external service?

How do I stub a response instead of querying $resource that depends on an external service? I'm getting an error: Error: Unexpected request: GET data/counties.json
Here's my service:
angular.module('myApp.services', ['ngResource']).
factory("geoProvider", function($resource){
return {
states: $resource('data/states.json', {}, {
query: {method: 'GET', params: {}, isArray: false}
}),
counties: $resource('data/counties.json', {}, {
query: {method: 'GET', params: {}, isArray: false}
})
};
});
Spec:
describe('service', function() {
beforeEach(module('myApp.services'));
describe('geoProvider', function() {
it('should return data / JSON', inject(function(geoProvider){
var data = geoProvider.counties.query();
expect(data).toBe(some);
}));
});
});
You can inject $httpBackend into your tests and configure it to expect the requests you need:
describe('service', function() {
var $httpBackend;
beforeEach(module('myApp.services'));
beforeEach(inject(function(_$httpBackend_) {
$httpBackend = _$httpBackend_;
}));
it('should return data / JSON', inject(function(geoProvider){
$httpBackend.expectGET('yoururl').respond({/* the response object */});
var data = geoProvider.counties.query();
$httpBackend.flush();
expect(data).toBe(some);
}));
});