How to unit test the angularjs injector, outside of a controller? - angularjs

As part of an AngualrJS app, I have to retrieve user details from a REST api before bootstrapping the application.
var initInjector = angular.injector(['ng']);
var $http = initInjector.get('$http');
$http.get('../api/user').then(
function (response) {
if (response.user.valid) {
angular.bootstrap(document, ['app']);
}
},
function () {
window.location.href = '../userError.html';
}
});
I'm building the application using gulp, and have been trying to unit test this as follows
describe('unit test app config', function () {
beforeEach(function () {
module('app');
inject();
});
it('should log a message on calling the injector', function () {
spyOn(angular, 'injector').and.callThrough();
expect(angular.injector).toHaveBeenCalled();
});
});
but this is giving the following error: Expected spy injector to have been called.
How can I unit test this code? I just need to mock the $http service and test the success and failure functions

Related

Unit Test AngularJs Jasmine error: Expected spy login to have been called

I'm new testing with AngularJs and Jasmine. I'm trying to test a login POST $http service. I'm not pretty sure how to do it, but what I have I'm getting an error and I dont know why.
This is my login.service.js:
(function () {
'use strict';
angular
.module('app')
.service('loginService', loginService);
/** #ngInject */
function loginService($http) {
var url = 'http://localhost:8080/login';
var service = {
login: login
};
return service;
// ////////// //
function login(user) {
return $http.post(url, user);
}
}
})();
and this is my test:
describe('login component', function () {
var loginService;
var httpBackend;
var user = {
username: 'ADMIN',
password: 'ADMIN'
};
beforeEach(module('app'));
beforeEach(angular.mock.inject(function (_$httpBackend_, _loginService_) {
loginService = _loginService_;
httpBackend = _$httpBackend_;
}));
it('loginService should be defined', function () {
expect(loginService).toBeDefined();
});
it('loginService.login should be defined', function () {
expect(loginService.login).toBeDefined();
});
describe('We call the Login Service', function () {
beforeEach(function () {
spyOn(loginService, "login").and.callThrough();
});
it('we call the service', function () {
loginService.login(user);
});
it('we look if we called the login service', function () {
expect(loginService.login).toHaveBeenCalled();
});
it('loginService login we send the correct parameters', function () {
expect(loginService.login).toHaveBeenCalledWith('http://localhost:8080/login', 'POST', user);
});
});
});
I'm getting the next error when it runs:
PhantomJS 2.1.1 (Linux 0.0.0) login component We call the Login Service we look if we called the login service FAILED
Expected spy login to have been called.
.tmp/app/login/login.spec.js:37:50
loaded#http://localhost:9876/context.js:151:17
PhantomJS 2.1.1 (Linux 0.0.0) login component We call the Login Service loginService login we send the correct parameters FAILED
Error: <toHaveBeenCalledWith> : Expected a spy, but got Function.
Usage: expect(<spyObj>).toHaveBeenCalledWith(...arguments) in node_modules/jasmine-core/lib/jasmine-core/jasmine.js (line 3340)
.tmp/app/login/login.spec.js:41:54
loaded#http://localhost:9876/context.js:151:17
PhantomJS 2.1.1 (Linux 0.0.0): Executed 6 of 6 (2 FAILED) (0.041 secs / 0.046 secs)
Does anybody know what I'm doing wrong??
THANKS!!!!
You're testing it all wrong. Since you're testing the loginService and login is a method defined on that service, you shouldn't mock the method login.
You should only be mocking methods that don't belong to the code(a service in this case) that you're testing. So according to this, you should be mocking the post call that you perform on the $http service.
That can be done like this:
describe('login component', function () {
var loginService;
var $httpBackend;
var user = {
username: 'ADMIN',
password: 'ADMIN'
};
beforeEach(module('app'));
beforeEach(angular.mock.inject(function (_$httpBackend_, _loginService_) {
loginService = _loginService_;
$httpBackend = _$httpBackend_;
$httpBackend.whenPOST('http://localhost:8080/login', user).respond(201, 'SUCCESS');
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
/**
* Ideally, these should be inside a seperate describe block.
*/
// it('loginService should be defined', function () {
// expect(loginService).toBeDefined();
// });
// it('loginService.login should be defined', function () {
// expect(loginService.login).toBeDefined();
// });
//Like this.
describe('Initialization', function(){
it('loginService should be defined', function () {
expect(loginService).toBeDefined();
});
it('loginService.login should be defined', function () {
expect(loginService.login).toBeDefined();
});
});
describe('function: login', function () {
/*
* We should not be spying a method from the service we're trying to test.
* This is anti-pattern. We should be mocking the post call instead.
*/
// beforeEach(function () {
// spyOn(loginService, "login").and.callThrough();
// });
/**
* This test is not doing anything. Each it block should have atleast one expect.
*/
// it('we call the service', function () {
// loginService.login(user);
// });
/**
* This isn't what should be expected here. We should call the method and expect some response.
* The response will be mocked by us using $httpBackend's expectPOST method.
*/
// it('we look if we called the login service', function () {
// expect(loginService.login).toHaveBeenCalled();
// });
// it('loginService login we send the correct parameters', function () {
// expect(loginService.login).toHaveBeenCalledWith('http://localhost:8080/login', 'POST', user);
// });
it('should respond with status 201 and data \'SUCCESS\'', function(){
var response = loginService.login(user);
$httpBackend.flush();
response.success(function(res){
expect(res).toEqual("SUCCESS");
});
});
});
});
Now you would find that I've commented most of your code. And that's because the practice that has been followed is all wrong.
The practice that should be followed is, you should be using multiple describe blocks for different parts of you code. That's what I've done. Please do go through the comments that I've given. That would help you understand better.
Hope this helps!
Check this out Expected a spy, but got Function
I believe your error is that the method you're after with your spy is actually on the prototype. You'll see in the error trail the Expected a Spy but got a Function. Try spying on the prototype instead:
beforeEach(function () {
spyOn(loginService.prototype, "login").and.callThrough();
});
it('we look if we called the login service', function () {
expect(loginService.prototype.login).toHaveBeenCalled();
});
In your case, since you use $http service, you may assert with expectPOST() provided in ngMock, see more in angularjs docs, read the examples in the docs!

How to write test case for JSON getting form factory in AngularJS

I am trying to write the test cass for the factory which is returing a JSON response.
But I am getting the error:
Error: [$injector:unpr] http://errors.angularjs.org/1.4.1/$injector/unpr?p0=serviceProvider%20%3C-%20service
at Error (native)
Here is my code:
(function () {
angular.module('uspDeviceService',[]).factory('getDevice', GetDevice);
GetDevice.$inject = ['$http'];
function GetDevice($http) {
getDeviceList = function() {
return $http.get("static/test-json/devices/device-list.json");
}
return {
getDeviceList: getDeviceList
}
}
}());
Code for Test case:
describe('Get Product test', function() {
beforeEach(module('uspDeviceService'));
var service, httpBackend, getDevice ;
beforeEach(function () {
angular.mock.inject(function ($injector) {
//Injecting $http dependencies
httpBackend = $injector.get('$httpBackend');
service = $injector.get('service');
getDevice = $injector.get('getDevice');
})
});
console.log('Injection Dependencies is done');
describe('get Device List', function () {
it("should return a list of devices", inject(function () {
httpBackend.expectGET("static/test-json/devices/device-list.json").respond("Response found!");
httpBackend.flush();
}))
})
});
I am new to Angular Unit testing, can anyone please help me, where I am going wrong..
Two things that jump out at me:
Your angular.module declaration is defining a module, not getting the module. I would encourage you to split that up so that it's a fair bit more clear what your intent is.
angular.module('uspDeviceService', []);
angular.module('uspDeviceService').factory('getDevice', GetDevice);
It likely works as-is, but clarity is important.
What is...service? It's not defined anywhere in your code, and Angular can't find it either, hence the error message. You may be looking to get getDevice instead. Also, name your test variable with respect to what it actually is, so you don't confuse yourself.
// defined above
var getDevice;
// while injecting
getDevice = $injector.get('getDevice');
Supposing that you have an angularjs controller myController defined in myModule. The controller do some action when the api call is success and shows a flash message when api returns success = false. The your controller code would be something like
angular.module('myModule')
.controller( 'myController', function ( $scope,flashService, Api ) {
Api.get_list().$promise.then(function(data){
if(data.success) {
$scope.data = data.response
}
else{
flashService.createFlash(data.message, "danger");
}
});
});
Now to test both success = true and success = false we
describe('myController', function(){
var $rootScope, $httpBackend, controller, flashService;
var apilink = 'http://apilink';
beforeEach(module('myModule'));
beforeEach(inject(function(_$httpBackend_,_$rootScope_, _$controller_, _flashService_) {
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
flashService = _flashService_;
controller = _$controller_("myController", {$scope: $rootScope});
}));
it('init $scope.data when success = true', function(){
$httpBackend.whenGET(apilink)
.respond(
{
success: true,
response: {}
});
$httpBackend.flush();
expect($rootScope.data).toBeDefined();
});
it('show flash when api request failure', function(){
spyOn(flashService, 'createFlash');
$httpBackend.whenGET(apilink)
.respond(
{
success: false
});
$httpBackend.flush();
expect(flashService.createFlash).toHaveBeenCalled();
});
});
You are always going to mock the response because here we are testing the javascript code behaviour and we are not concerned with the Api. You can see when success the data is initialized and when success is false createFlash is called.
As far as test for factory is concerned you can do
describe('Get Product test', function() {
beforeEach(module('uspDeviceService'));
var service, httpBackend, getDevice ;
beforeEach(function () {
inject(function ($injector) {
httpBackend = $injector.get('$httpBackend');
service = $injector.get('service');
getDevice = $injector.get('getDevice');
});
});
describe('get Device List', function () {
it("should return a list of devices", inject(function () {
httpBackend.expectGET("static/test-json/devices/device- list.json").respond("Response found!");
var result = getDevice.getDeviceList();
httpBackend.flush();
expect(result).toEqual('Response found!');
}));
});
});

Calling a method from an injected service in Jasmine

I'm attempted to unit test a service. I've injected the service however the method call getAllProducts() doesn't appear to run however the test still passes!
Plnkr
service.js
angular.module('vsApp')
.factory('productsDataService', function($http) {
var service = {
getAllProducts: getAllProducts
};
// get all products
function getAllProducts() {
return $http.get('/apiv1/getAllProducts/').then(function(data) {
return (data);
});
}
return service;
});
spec.js
// jasmine
describe('products data service', function () {
var $httpBackend, productsDataService;
beforeEach(module('vsApp'));
beforeEach(inject(function(_$httpBackend_, _productsDataService_) {
$httpBackend = _$httpBackend_;
productsDataService = _productsDataService_;
}));
it('should get all products', inject(function() {
console.info("get all");
// mock response for the http call in the service
$httpBackend.when('GET', '/apiv1/getAllProducts/')
.respond({name: 'item', price: '932'});
//this doesn't seem to run??
productsDataService.getAllProducts().then(function(response) {
expect(response.data.length).toBeGreaterThan(1);
});
}));
});
Ok, you have to make it sync. (all pending request will get resolved) using $http.flush();
Working demo as expected
productsDataService.getAllProducts().then(function(response) {
console.log(response);
expect(response.data.length).toBeGreaterThan(999);
});
$httpBackend.flush(); // <=============== here.

Unit test a service with http request only angularjs

I have a function called getFrame in a service. The function just returns the $http call to controller.
angular.module('app').factory('DemoFactory', function ($http) {
function getFrame(id) {
var url = 'http://localhost:8080/frames/' + id + '/';
return $http.get(url);
}
return {
getFrame: getFrame
};
});
Now I want to write unittest for this which I am doing as follows:
describe('Service: DemoFactory', function () {
// load the service's module
beforeEach(module('app'));
// Instantiate service
var $httpBackend,
DemoFactory;
beforeEach(inject(function (_$httpBackend_, _DemoFactory_) {
$httpBackend = _$httpBackend_;
DemoFactory = _DemoFactory_;
}));
it('should send proper http request from getFrame', function () {
$httpBackend.expectGET('http://localhost:8080/frames/1/').respond(200);
DemoFactory.getFrame(1);
$httpBackend.flush();
});
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
});
With the given service my aim is to test whether getFrame is making a proper http request or not. So I think I am doing OK here. But something made me wonder that it block is not having any expect. So I need to confirm that for the service I have written I can have unit test as described. Do I need to have anything else in the unit test or can I do it any other way?

testing Angular async services with Jasmine

I am trying to test a real http call with Jasmine (integration test), but when i call a method that uses $http.get, it times out and the server never gets called.
I know that I am supposed to inject the implementation of $http but not sure where that should happen.
searchSvc
app.service('searchSvc', ['$http', '$q', searchSvc]);
function searchSvc($http, $q) {
return {
search: function(text) {
console.log('svc.search called with ', text); // this does get called
return $q.when($http.get('/search/' + text));
}
};
}
searchSpec
describe("searchTest", function() {
var ctrl, svc, $http;
beforeEach(function () {
module('testApp');
inject(function(_$controller_, searchSvc, _$http_){
ctrl = _$controller_('searchCtrl');
svc = searchSvc;
$http = _$http_;
})
});
it('test server search', function(done) {
svc.search('re').then(function(result) {
console.log('promise then'); // this never gets called, because server never gets called
expect(result).not.toBeNull();
expect(result.data).not.toBeNull();
expect(result.data.length).toBeGreaterThan(0);
done();
});
});
In case if you use promises you can find out how to deal with them here http://entwicklertagebuch.com/blog/2013/10/how-to-handle-angularjs-promises-in-jasmine-unit-tests/
This is sort of hypothetical, but if you include both ngMock & ngMockE2E modules as your app module's dependency (ngMock needs to come before ngMockE2E in the dependency list) you should be able to use $httpBackend service provided by ngMockE2E module to passThrough the search api call to actual backend in your test specs.
Try something like this and see whether it works:
describe("searchTest", function() {
var ctrl, svc, $httpBackend;
beforeEach(function () {
module('testApp');
inject(function(_$controller_, searchSvc, _$httpBackend_){
$httpBackend = _$httpBackend_;
ctrl = _$controller_('searchCtrl');
svc = searchSvc;
});
});
it('test server search', function(done) {
$httpBackend.whenGET(/^\/search\//).passThrough();
svc.search('re').then(function(result) {
console.log('promise then'); // this never gets called, because server never gets called
expect(result).not.toBeNull();
expect(result.data).not.toBeNull();
expect(result.data.length).toBeGreaterThan(0);
done();
});
});
});
Here is a solution that I use to make real HTTP calls when I'm using ngMock for unit tests. I mainly use it for debugging, working through the test, getting JSON examples etc.
I wrote a more detailed post about the solution on my blog: How to Unit Test with real HTTP calls using ngMockE2E & passThrough.
The solution is as follows:
angular.mock.http = {};
angular.mock.http.init = function() {
angular.module('ngMock', ['ng', 'ngMockE2E']).provider({
$exceptionHandler: angular.mock.$ExceptionHandlerProvider,
$log: angular.mock.$LogProvider,
$interval: angular.mock.$IntervalProvider,
$rootElement: angular.mock.$RootElementProvider
}).config(['$provide', function($provide) {
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);
$provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator);
$provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
$provide.decorator('$controller', angular.mock.$ControllerDecorator);
}]);
};
angular.mock.http.reset = function() {
angular.module('ngMock', ['ng']).provider({
$browser: angular.mock.$BrowserProvider,
$exceptionHandler: angular.mock.$ExceptionHandlerProvider,
$log: angular.mock.$LogProvider,
$interval: angular.mock.$IntervalProvider,
$httpBackend: angular.mock.$HttpBackendProvider,
$rootElement: angular.mock.$RootElementProvider
}).config(['$provide', function($provide) {
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);
$provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator);
$provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
$provide.decorator('$controller', angular.mock.$ControllerDecorator);
}]);
};
Include this source file after ngMock, for example:
<script type="text/javascript" src="angular.js"></script>
<script type="text/javascript" src="angular-mocks.js"></script>
<!-- this would be the source code just provided -->
<script type="text/javascript" src="ngMockHttp.js"></script>
How to write the test?
describe('http tests', function () {
beforeEach(module('moviesApp'));
var $controller;
var $httpBackend;
var $scope;
describe('real http tests', function() {
beforeEach(angular.mock.http.init);
afterEach(angular.mock.http.reset);
beforeEach(inject(function(_$controller_, _$httpBackend_) {
$controller = _$controller_;
$scope = {};
$httpBackend = _$httpBackend_;
// Note that this HTTP backend is ngMockE2E's, and will make a real HTTP request
$httpBackend.whenGET('http://www.omdbapi.com/?s=terminator').passThrough();
}));
it('should load default movies (with real http request)', function (done) {
var moviesController = $controller('MovieController', { $scope: $scope });
setTimeout(function() {
expect($scope.movies).not.toEqual([]);
done();
}, 1000);
});
});
});
How it works?
It uses ngMockE2E's version of $httpBackEndProvider, which provides us with the passThrough function we see being used in the test. This does as the name suggests and lets a native HTTP call pass through.
We need to re-define the ngMock module without its fake version of the $BrowserProvider, since that is what prevents the real HTTP calls in unit tests that use ngMock.

Resources