Unit testing the AngularJS $window service - angularjs

I would like to unit test the following AngularJs service:
.factory('httpResponseInterceptor', ['$q', '$location', '$window', 'CONTEXT_PATH', function($q, $location, $window, contextPath){
return {
response : function (response) {
//Will only be called for HTTP up to 300
return response;
},
responseError: function (rejection) {
if(rejection.status === 405 || rejection.status === 401) {
$window.location.href = contextPath + '/signin';
}
return $q.reject(rejection);
}
};
}]);
I have tried with the following suite:
describe('Controllers', function () {
var $scope, ctrl;
beforeEach(module('curriculumModule'));
beforeEach(module('curriculumControllerModule'));
beforeEach(module('curriculumServiceModule'));
beforeEach(module(function($provide) {
$provide.constant('CONTEXT_PATH', 'bignibou'); // override contextPath here
}));
describe('CreateCurriculumCtrl', function () {
var mockBackend, location, _window;
beforeEach(inject(function ($rootScope, $controller, $httpBackend, $location, $window) {
mockBackend = $httpBackend;
location = $location;
_window = $window;
$scope = $rootScope.$new();
ctrl = $controller('CreateCurriculumCtrl', {
$scope: $scope
});
}));
it('should redirect to /signin if 401 or 405', function () {
mockBackend.whenGET('bignibou/utils/findLanguagesByLanguageStartingWith.json?language=fran').respond([{"description":"Français","id":46,"version":0}]);
mockBackend.whenPOST('bignibou/curriculum/new').respond(function(method, url, data, headers){
return [401];
});
$scope.saveCurriculum();
mockBackend.flush();
expect(_window.location.href).toEqual("/bignibou/signin");
});
});
});
However, it fails with the following error message:
PhantomJS 1.9.2 (Linux) Controllers CreateCurriculumCtrl should redirect to /signin if 401 or 405 FAILED
Expected 'http://localhost:9876/context.html' to equal '/bignibou/signin'.
PhantomJS 1.9.2 (Linux) ERROR
Some of your tests did a full page reload!
I am not sure what is going wrong and why. Can anyone please help?
I just want to ensure the $window.location.href is equal to '/bignibou/signin'.
edit 1:
I managed to get it to work as follows (thanks to "dskh"):
beforeEach(module('config', function($provide){
$provide.value('$window', {location:{href:'dummy'}});
}));

You can inject stub dependencies when you load in your module:
angular.mock.module('curriculumModule', function($provide){
$provide.value('$window', {location:{href:'dummy'}});
});

To get this to work for me I had to make a minor adjustment. It would error out and say:
TypeError: 'undefined' is not an object (evaluating '$window.navigator.userAgent')
So I added the navigator.userAgent object to get it to work for me.
$provide.value('$window', {
location:{
href:'dummy'
},
navigator:{
userAgent:{}
}
});

I faced the same problem, and went a step further in my solution. I didn't just want a mock, I wanted to replace $window.location.href with a Jasmine spy for the better ability to track changes made to it. So, I learned from apsiller's example for spying on getters/setters and after creating my mock, I was able to spy on the property I wanted.
First, here's a suite that shows how I mocked $window, with a test to demonstrate that the spy works as expected:
describe("The Thing", function() {
var $window;
beforeEach(function() {
module("app", function ($provide) {
$provide.value("$window", {
//this creates a copy that we can edit later
location: angular.extend({}, window.location)
});
});
inject(function (_$window_) {
$window = _$window_;
});
});
it("should track calls to $window.location.href", function() {
var hrefSpy = spyOnProperty($window.location, 'href', 'set');
console.log($window.location.href);
$window.location.href = "https://www.google.com/";
console.log($window.location.href);
expect(hrefSpy).toHaveBeenCalled();
expect(hrefSpy).toHaveBeenCalledWith("https://www.google.com/");
});
});
As you can see above, the spy is generated by calling the below function: (it works for both get and set)
function spyOnProperty(obj, propertyName, accessType) {
var desc = Object.getOwnPropertyDescriptor(obj, propertyName);
if (desc.hasOwnProperty("value")) {
//property is a value, not a getter/setter - convert it
var value = desc.value;
desc = {
get: function() { return value; },
set: function(input) { value = input; }
}
}
var spy = jasmine.createSpy(propertyName, desc[accessType]).and.callThrough();
desc[accessType] = spy;
Object.defineProperty(obj, propertyName, desc);
return spy;
}
Lastly, here's a fiddle demonstrating this in action. I've tested this against Angular 1.4, and Jasmine 2.3 and 2.4.

Related

Unsure on how to successfully test this function using $httBackend

This is the function in the controller:
var vm = this;
vm.getData = getData;
function getData(val) {
return $http.get('/get-data', {
params: {
query: val
}
}).then(function(response) {
return response.data;
});
}
and this is my (stripped down) test file:
describe('Controller: MyCtrl', function() {
'use strict';
var MyCtrl;
var rootScope;
var scope;
var httpMock;
beforeEach(function() {
module('MyModule');
inject(function($controller, $rootScope, $httpBackend) {
rootScope = $rootScope;
scope = $rootScope.$new();
httpMock = $httpBackend;
MyCtrl = $controller('MyCtrl as vm', {
$rootScope: rootScope,
$scope: scope,
$http: httpMock,
});
});
});
describe('vm.getData()', function() {
it('returns the required data', function() {
httpMock.when('GET', '/get-data?query=test-val').respond(200, {data: 'test-data'});
httpMock.flush();
expect(scope.vm.getData('test-val')).toEqual('test-data');
});
});
});
I would like to test that the result if calling getData() return the correct data.
Currently I'm getting the error $http.get is not a function. Setting a breakpoint in my function shows that http is stubbed with $httpBackend though.
I think there is something fundamental I'm not grasping - any pointers would be greatly appreciated.
You shouldn't have to create the controller with:
$http: $httpBackend
to mock the backend. $httpBackend will mock the request itself already.
Further the test and assertion is done in the wrong order:
httpMock.when('GET', '/get-data?query=test-val').respond(200, {data: 'test-data'});
MyCtrl.getData('test-val').then(function(_result_){ //perform the request
result = _result_; //save the result of the promise
});
httpMock.flush(); //execute the request
expect(result).toBe('test-data'); //assert that the result is as expected

Unit test an angular controller and service which uses a promise?

I cannot get the test result to pass I'm using a very basic implementation to understand testing deeper.
I have a factory which returns a promise, accessed from my controller. I want to test that the call succeeds and assigns the response to the repos var. Following is the code:
'use strict';
angular.module('app')
.factory('searchServ', function ($timeout, $q, $http) {
return {
fetch: function(user) {
var deferred = $q.defer();
$timeout(function(){
$http({method: 'GET', url: 'https://api.github.com/users/' + user + '/repos'}).then(function(repos) {
deferred.resolve(repos.data);
}, function(reason){
deferred.reject(reason.status);
console.log(reason);
});
}, 30);
return deferred.promise;
}
};
})
.controller('MainCtrl', function ($scope, searchServ) {
$scope.results = function(user) {
$scope.message = '';
searchServ.fetch(user).then(function (repos) {
if(repos.length){
$scope.message = '';
$scope.repos = repos;
}
else{
$scope.message = 'not found'
}
}, function (){
$scope.message = 'not found';
});
};
});
//Test
'use strict';
describe('MainCtrl', function () {
var scope, searchServ, controller, deferred, repos = [{name: 'test'}];
// load the controller's module
beforeEach(module('app'));
beforeEach(inject(function($controller, $rootScope, $q) {
searchServ = {
fetch: function () {
deferred = $q.defer();
return deferred.promise;
}
};
spyOn(searchServ, 'fetch').andCallThrough();
scope = $rootScope.$new();
controller = $controller('MainCtrl', {
$scope: scope,
fetchGithub: fetchGithub
});
}));
it('should test', function () {
expect(scope.test).toEqual('ha');
});
it('should bind to scope', function () {
scope.results();
scope.$digest();
expect(scope.message).toEqual('');
//expect(scope.repos).not.toBe(undefined);
});
});
Running the test gives me the following error :
TypeError: undefined is not a function (evaluating 'spyOn(searchServ, 'fetch').andCallThrough()') in test/spec/controllers/main.js (line 15)
Any idea how I can test this such that it tests the scope binding as well as the async call?
There are a lot of issues with your code.
I've created this Plunkr for the purpose. index.js is the file with your code and test cases. I've edited most of the part according to the conventions and best-practices.
There are a few pointers I wanted to give you:
Since $http returns a promise, you should use that, instead of resolving the promise and creating another promise from your method. Not sure why is timeout used. So I removed $q and $timeout from searchServ's dependencies.
I did the same in the test case by removing the deferred variable that you used.
You should be using angular-mocks.js to mock your services and other dependencies instead of defining a service inside your test case(The way you have did.)
You should create separate describe blocks for testing different parts of your code(a controller in this case).
Hope this helps!

How to get unit test working with $httpBackend?

I am just starting getting my head around unit testing and angular 1.2. I am using the $httpBackend service for testing a $httpcall in a service. This service is called inside my controller:
var app = angular.module('myApp', []);
app.controller('MainCtrl', function ( someService) {
var vm = this;
vm.hasError = false;
//debugger;
someService.someAsyncCall()
.then(function (data) {
vm.hasError = false;
})
.catch(function (data) {
vm.hasError = true;
});
});
app.factory('someService', function ($http) {
return {
someAsyncCall: function () {
return $http.get("/data")
.success(function (data) {
//return true
})
.error(function () {
console.log('error');
//return false
});
}
};
});
I am trying to write a test for when the endpoint is wrong:
beforeEach(function () {
someServiceMock = jasmine.createSpyObj('someService', ['someAsyncCall']);
module('myApp');
inject(function ($rootScope, $controller, $q, _$timeout_,$httpBackend) {
$scope = $rootScope.$new();
someServiceMock.someAsyncCall.andReturn($q.when('weee'));
$timeout = _$timeout_;
controllerService = $controller;
httpMock = $httpBackend;
});
});
it("should set hasError=true with error request", function () {
httpMock.expectGET("/datajfds").respond(false);
ctrl = controllerService('MainCtrl', {someService: someServiceMock});
expect(ctrl.hasError).toBe(true);
});
This is the error:
MainCtrl testing should set hasError=true with error request.
Expected false to be true.
Error: Expected false to be true.
at new jasmine.ExpectationResult (http://cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine.js:114:32)
at .toBe (http://cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine.js:1235:29)
at .<anonymous> (http://run.plnkr.co/Q77IyPb6agUqgR2U/specs.js:37:31)
Basically I would like to get coverage/write a test for when the someService.someAsyncCall() will catch an error. How can I write this test so that ctrl.hasError =true?
plunkr:http://plnkr.co/edit/aX29h4k64ZUm6hDedZl6?p=preview
There were lots of problems with what you were doing and I don't understand what you are trying to do with the third test. The api that you hit is configured in the service.
$http.get("/data")
See this plunker which might be that the tests that you were trying to do. Some problems:
You were injecting a lot of things that weren't needed
Your service call wasn't returning anything.
You weren't getting the value from the service call

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!');
}));
});
});

Jasmine doesn't execute callbacks from $resource

So I'm having this issue when writing my tests that I don't know how to solve:
This is my controller:
'use strict';
angular.module('testApp')
.controller('SettingsExtrasCtrl', function ($scope, $log, Auth, Property, $modal, dialogs, growl) {
$scope.deleteExtra = function(index) {
var dlg = dialogs.confirm('Please Confirm', 'Are you sure you want to delete '+$scope.selectedProperty.extras[index].name+'?');
dlg.result.then(function() {
Property.removeExtra({ _id : $scope.selectedProperty._id, otherId : $scope.selectedProperty.extras[index]._id }, function(res) {
$scope.selectedProperty.extras.splice(index,1);
growl.success("Success message", {title : 'Success'});
},
function(err) {
console.log(err);
});
});
};
});
$scope.selectedProperty comes from a parent controller.
And here is my test:
'use strict';
describe('Controller: SettingsExtrasCtrl', function () {
// load the controller's module
beforeEach(module('testApp'));
var SettingsExtrasCtrl, scope, stateParams, Property, httpBackend;
var dialogs = {
confirm: function (title, message) {
return {
result: {
then: function (callback) {
return callback();
}
}
}
}
};
var fakeProperty = {
_id : 'propertyId',
extras : [
{
_id : 'extraId',
name : 'Extra'
}
]
};
beforeEach(inject(function ($controller, $rootScope, _Property_, _$httpBackend_, $state, $modal, _dialogs_) {
scope = $rootScope.$new();
scope.selectedProperty = fakeProperty;
stateParams = {propertyId: fakeProperty._id};
Property = _Property_;
httpBackend = _$httpBackend_;
spyOn(Property, 'removeExtra');
spyOn(_dialogs_, 'confirm').andCallFake(dialogs.confirm);
SettingsExtrasCtrl = $controller('SettingsExtrasCtrl', {
$scope: scope,
$stateParams: stateParams,
dialogs: _dialogs_,
$state: $state
});
}));
it('should delete an extra', inject(function(_dialogs_) {
httpBackend.expectDELETE('/api/properties/' + stateParams.propertyId + '/extras/someextraId').respond(200, '');
scope.deleteExtra(0);
expect(_dialogs_.confirm).toHaveBeenCalled();
expect(Property.removeExtra).toHaveBeenCalled();
expect(scope.selectedProperty.extras.length).toBe(0);
}));
});
The assert expect(scope.selectedProperty.extras.length).toBe(0); fails because expects 1 to be 0 because the success callback from Property.removeExtra is never called.
Any idea on how to solve this?
Thanks.
For promise to be executed you have to call a digest cycle :
scope.deleteExtra(0);
scope.$digest();
[EDIT]
Has it's a network call, you will have to look at $httpBackend
basically it work like that :
//you can mock the network call
$httpBackend.whenGET('https://url').respond(200);//status code or object
// stuff that make the call
....
$httpBackend.flush();
expect(thing).toBe(stuff);
A bit of doc :
The $httpBackend used in production always responds to requests asynchronously. If we preserved this behavior in unit testing, we'd have to create async unit tests, which are hard to write, to follow and to maintain. But neither can the testing mock respond synchronously; that would change the execution of the code under test. For this reason, the mock $httpBackend has a flush() method, which allows the test to explicitly flush pending requests. This preserves the async api of the backend, while allowing the test to execute synchronously.

Resources