Testing $http calls in Angular 1.1.5 - unit-testing

I'm running into trouble trying to mock my $http calls in Angular. When calling $httpBackend.flush() I'm getting the error: Error: No pending request to flush !
I've read up on many other posts of similar issues on here, but I still can't seem to get it to work by adding $rootScope.$digest() or adding $httpBackend.expect
I'm testing a service function:
getAll: function (success, error) {
$http.get(apiRootService.getAPIRootHTTP() + "/items")
.success(success).error(error);
},
Using this .spec:
describe ('itemAPI Module', function (){
var $httpBackend, $rootScope, itemAPI, apiRootService;
var projectID = 123;
beforeEach(module('itemAPI'));
beforeEach(module('mApp'));
/**
* Set up variables pre-insertion
*/
beforeEach( inject( function(_$rootScope_, _$httpBackend_, _apiRootService_){
$httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;
apiRootService = _apiRootService_;
}));
describe('itemAPI factory', function (){
it('can get an instance of the metaAPI factory', inject(function(_itemAPI_){
itemAPI = _itemAPI_;
expect(metaAPI).toBeDefined();
}));
describe("get all items", function (){
it("will return an error", function (){
var url = apiRootService.getAPIRootHTTP() + "/items";
$httpBackend.whenGET(url)
.respond(400);
var error;
itemAPI.getAll(
function(data, status){
//this is success
},
function(data, status){
error = true;
});
$httpBackend.expectGET(url);
$rootScope.$digest();
$httpBackend.flush();
expect(error).toBeTruthy();
});
});
});
});

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

Unable to do unit testing for an API in sequelize using Mocha and Chai

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?

multiple get requests one controller: Unexpected request, unit testing - AngularJS

I can find plenty of examples of single http calls from a controller and how to test them,but no examples of multiple testing.
My first test works fine without Product.find(10) in the controller. When I add that line however the first test collapses.
The errors:
Error: Unexpected request: GET 0.0.0.0:3000/api/products
No more request expected
and
Error: Unexpected request: GET 0.0.0.0:3000/api/products
No more request expected
I've tried a number of things: including both in the before each, this gave me an undefined error, i tried using expect instead of when, I tried adding both whens to both tests, and a combination of the above. I'm clearly doing something very wrong but being an angular newbie, it's hard to work out exactly what that might be, especially with the lack of examples.. I am just looking to get my first test to pass with Product.find(10)
Here are my tests:
'use strict';
describe('productsController', function() {
var scope, $httpBackend;
var api_root = '0.0.0.0:3000/api/';
beforeEach(angular.mock.module('sprangularApp'));
beforeEach(angular.mock.inject( function($rootScope, $controller, _$httpBackend_) {
$httpBackend = _$httpBackend_;
//Get mock jsons
jasmine.getJSONFixtures().fixturesPath='base/js/tests/api_mock';
scope = $rootScope.$new();
$controller('productsController', {$scope: scope});
}));
//Start Tests
it('Should be array of all products', function() {
$httpBackend.when('GET', api_root + 'products').respond(
getJSONFixture('products.json')
);
$httpBackend.flush();
expect(scope.products[3].name).toBe('Ruby on Rails Bag');
});
it('Should instantiate a new product object from json data', function() {
$httpBackend.when('GET', api_root + 'products/10').respond(
getJSONFixture('10.json')
);
$httpBackend.flush();
expect(scope.currentProduct.name).toBe('Spree Ringer T-Shirt');
});
});
my controller that I am testing:
// Generated by CoffeeScript 1.6.3
(function() {
var sprangularControllers;
sprangularControllers = angular.module('sprangularControllers', ['sprangularServices']);
sprangularControllers.controller('productsController', [
'$scope', 'Product', function($scope, Product) {
Product.products_with_meta().$promise.then(function(response) {
return $scope.products = response.products;
});
return Product.find(10);
}
]);
}).call(this);
And the factory with the resource requests:
sprangularServices = angular.module('sprangularServices', ['ngResource'])
sprangularServices.factory('Defaults', ->
api_url: "0.0.0.0:3000/api/"
)
sprangularServices.factory('Product', ($resource, Defaults) ->
# $resource(Defaults.api_url + 'products.json')
class Product
constructor: ->
#service = $resource(Defaults.api_url + 'products/:id', {id: '#id'})
this.products_with_meta = ->
service = $resource(Defaults.api_url + 'products')
service.get()
this.find = (id) ->
service = $resource(Defaults.api_url + 'products/:id', {id: id})
service.get()
)
As per michael's suggestion I have edited my test to this, however I am still getting the exact same result:
'use strict';
describe('productsController', function() {
var $rootScope, $httpBackend, createController;
var api_root = '0.0.0.0:3000/api/';
beforeEach(angular.mock.module('sprangularApp'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
//Get mock jsons
jasmine.getJSONFixtures().fixturesPath='base/js/tests/api_mock';
$rootScope = $injector.get('$rootScope');
var $controller = $injector.get('$controller');
createController = function() {
return $controller('productsController', {'$scope' : $rootScope });
};
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
//Start Tests
it('Should be array of all products', function() {
$httpBackend.when('GET', api_root + 'products').respond(
getJSONFixture('products.json')
);
var controller = createController();
$httpBackend.flush();
expect($rootScope.products[3].name).toBe('Ruby on Rails Bag');
});
it('Should instantiate a new product object from json data', function() {
$httpBackend.when('GET', api_root + 'products/10').respond(
getJSONFixture('10.json')
);
var controller = createController();
$httpBackend.flush();
expect($rootScope.currentProduct.name).toBe('Spree Ringer T-Shirt');
});
});
Structuring my test in this way seemed to solve the issue:
'use strict';
describe('productsController', function() {
var $rootScope, $httpBackend, createController;
var api_root = '0.0.0.0:3000/api/';
beforeEach(angular.mock.module('sprangularApp'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
//Get mock jsons
jasmine.getJSONFixtures().fixturesPath='base/js/tests/api_mock';
$rootScope = $injector.get('$rootScope');
var $controller = $injector.get('$controller');
createController = function() {
return $controller('productsController', {'$scope' : $rootScope });
};
$httpBackend.when('GET', api_root + 'products').respond(
getJSONFixture('products.json')
);
$httpBackend.when('GET', api_root + 'products/10').respond(
getJSONFixture('10.json')
);
var controller = createController();
$httpBackend.flush();
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
$httpBackend.resetExpectations();
});
//Start Tests
it('Should be array of all products', function() {
expect($rootScope.products[3].name).toBe('Ruby on Rails Bag');
});
it('Should instantiate a new product object from json data', function() {
expect($rootScope.currentProduct.name).toBe('Spree Ringer T-Shirt');
});
});
I suppose the order of define the response, do the http call, flush and do the test is not right.
define how the http call should respond
$httpBackend.when('GET', api_root + 'products').respond(
getJSONFixture('products.json')
);
do the call from your code
$controller('productsController', {$scope: scope});
flush the httpBackend (e.g. simulate the asynchronous behavior of $http)
$httpBackend.flush();
do the test
expect(scope.products[3].name).toBe('Ruby on Rails Bag');
because your controller did a backend call in his constructor and is instantiated before you define what the response should be, you got the error.
Further information and an exmaple the is very close to your use case: http://docs.angularjs.org/api/ngMock.$httpBackend

Testing asynchrone function gives Unexpected request

The unittest:
"use strict";
var usersJSON = {};
describe("mainT", function () {
var ctrl, scope, httpBackend, locationMock,
beforeEach(module("testK"));
beforeEach(inject(function ($controller, $rootScope, $httpBackend, $location, $injector) {
scope = $rootScope.$new();
httpBackend = $httpBackend;
locationMock = $location;
var lUrl = "../solr/users/select?indent=true&wt=json",
lRequestHandler = httpBackend.expect("GET", lUrl);
lRequestHandler.respond(200, usersJSON);
ctrl = $controller("mainT.controller.users", { $scope: scope, $location: locationMock});
httpBackend.flush();
expect(scope.users).toBeDefined();
}));
afterEach(function () {
httpBackend.verifyNoOutstandingRequest();
httpBackend.verifyNoOutstandingExpectation();
});
describe("method test", function () {
it('should test', function () {
expect(true).toBeFalsy();
});
});
});
controller I'm testing (working):
Asynchrone function in init who's giving me trouble (uses ../solr/users/select?indent=true&wt=json):
$scope.search = function () {
var lStart = 0,
lLimit = privates.page * privates.limit;
Search.get({
collection: "users",
start: lStart,
rows: lLimit)
}, function(records){
$scope.users= records.response.docs;
});
};
What I think happens:
1. inform backend what request he will receive
2. inform backend to response on that request with empty JSON
3. create a controller (Search.get get's executed)
4. inform backend to receive all requests and answer them (flush)
Yet I always get the following error:
Error: Unexpected request: GET : ../solr/users/select?indent=true&wt=json
Am I not handling the asynchrone search function well? how should this be done?
That's not really a "unit" test, it's more of a behavioral test.
This should really be a few tests:
Test your service Search.get to make sure it's calling the proper URL and returning the result.
Test your controller method to make sure it's calling Search.get
Test your controller method to make sure it's putting the result in the proper spot.
The code you've posted is a little incomplete, but here are two unit tests that should cover you:
This is something I've blogged about extensively, and the entries go into more detail:
Unit Testing Angular Controllers
Unit Testing Angular Services
Here's an example of what I'm talking about:
describe('Search', function () {
var Search,
$httpBackend;
beforeEach(function () {
module('myModule');
inject(function (_Search_, _$httpBackend_) {
Search = _Search_;
$httpBackend = _$httpBackend_;
});
});
describe('get()', function () {
var mockResult;
it('should call the proper url and return a promise with the data.', function () {
mockResult = { foo: 'bar' };
$httpBackend.expectGET('http://sample.com/url/here').respond(mockResult);
var resultOut,
handler = jasmine.createSpy('result handler');
Search.get({ arg1: 'wee' }).then(handler);
$httpBackend.flush();
expect(handler).toHaveBeenCalledWith(mockResult);
$httpBackend.verifyNoOutstandingRequest();
$httpBackend.verifyNoOutstandingExpectation();
});
});
});
describe('myCtrl', function () {
var myCtrl,
$scope,
Search;
beforeEach(function () {
module('myModule');
inject(function ($rootScope, $controller, _Search_) {
$scope = $rootScope.$new();
Search = _Search;
myCtrl = $controller('MyCtrl', {
$scope: scope
});
});
});
describe('$scope.foo()', function () {
var mockResult = { foo: 'bar' };
beforeEach(function () {
//set up a spy.
spyOn(Search, 'get').andReturn({
then: function (fn) {
// this is going to execute your handler and do whatever
// you've programmed it to do.. like $scope.results = data; or
// something.
fn(mockResult);
}
});
$scope.foo();
});
it('should call Search.get().', function () {
expect(Search.get).toHaveBeenCalled();
});
it('should set $scope.results with the results returned from Search.get', function () {
expect(Search.results).toBe(mockResult);
});
});
});
In a BeforeEach you should use httpBackend.when instead of httpBackend.expect. I don't think you should have an assertion (expect) in your BeforeEach, so that should be moved to a separate it() block. I also don't see where lRequestHandler is defined. The 200 status is sent by default so that is not needed. Your httpBackend line should look like this:
httpBackend.when("GET", "/solr/users/select?indent=true&wt=json").respond({});
Your test should then be:
describe("method test", function () {
it('scope.user should be defined: ', function () {
expect(scope.user).toEqual({});
});
});
Your lUrl in the unit test, shouldn't be a relative path, i.e., instead of "../solr/users/select?indent=true&wt=json" it should be an absolute "/solr/users/select?indent=true&wt=json". So if your application is running at "http://localhost/a/b/index.html", lUrl should be "/a/solr/...".
Note that you can also use regular expressions in $httpBackend.expectGET(), that could be helpful here in case you are not entirely sure how the absolute path will look like later on.

Unit testing AngularJS controller with $httpBackend

For the life of me I can't get $httpBackend to work on a controller that does an $http get request. I've tried for hours now =)
I've reduced this to the simplest form I can below. The test passes if I
comment out the $http.get() request in the controller
comment out the "httpMock.flush()" in the test
and change "pig" and "dog" to match
That is, it's a valid, working test and app.
If I put it back in, I get the error shown at the bottom.
app/js/app.js
// Declare a module which depends on filters and services.
var myApp = angular
.module('myApp', ['ngRoute', 'myApp.filters', 'myApp.services',
'myApp.directives'])
.config(['$routeProvider' , function($routeProvider) {
$routeProvider
.when("/dashboard", {
templateUrl: "partials/dashboard.html",
controller: cDashboard
})
.otherwise({redirectTo: "/dashboard"});
}]);
// Pre-define our main namespace modules.
angular.module('myApp.directives' , []);
angular.module('myApp.filters' , []);
angular.module('myApp.services' , []);
angular.module('myApp.controllers', []);
app/js/controller.js
function cDashboard ($scope, $http) {
$scope.data = "dog";
// Fetch the actual data.
$http.get("/data")
.success(function (data) { $scope.data = data })
.error(function () {});
}
cDashboard.$inject = [ '$scope', '$http' ];
test/unit/controllerSpec.js
describe('cDashboard', function(){
var scope, ctrl, httpMock;
beforeEach(inject(function ($rootScope, $controller, $http, $httpBackend) {
scope = $rootScope.$new();
ctrl = $controller('cDashboard', {$scope: scope});
httpMock = $httpBackend;
httpMock.when("GET", "/data").respond("pig");
}));
it("should get 'pig' from '/data'", function () {
httpMock.expectGET("/data").respond("pig");
expect(scope.data).toBe("pig");
});
});
And this is the error I get in the shell:
INFO [watcher]: Changed file "/home/myApp/test/unit/controller/cDashboard.js".
Chrome 26.0 (Linux) cDashboard should get 'pig' from '/data' FAILED
Error: No pending request to flush !
at Error (<anonymous>)
at Function.$httpBackend.flush (/home/myApp/test/lib/angular/angular-mocks.js:1171:34)
at null.<anonymous> (/home/myApp/test/unit/controller/cDashboard.js:15:18)
Chrome 26.0 (Linux): Executed 1 of 1 (1 FAILED) (0.326 secs / 0.008 secs)
There are a couple problems in your test code:
The controller is created before the httpMock is configured to respond with pig. The expectGet call should happen before instantiating the controller.
The httpMock needs to flush the request
The httMock.when is unnecessary so long as you have the expectGet
Working example: http://plnkr.co/edit/lUkDMrsy1KJNai3ndtng?p=preview
describe('cDashboard', function(){
var scope, controllerService, httpMock;
beforeEach(inject(function ($rootScope, $controller, $httpBackend) {
scope = $rootScope.$new();
controllerService = $controller;
httpMock = $httpBackend;
}));
it("should get 'pig' from '/data'", function () {
httpMock.expectGET("/data").respond("pig");
ctrl = controllerService('cDashboard', {$scope: scope});
httpMock.flush();
expect(scope.data).toBe("pig");
});
});