Angular Jasmine test response interceptor - angularjs

I'm trying to test my response interceptor but I have a hard time figuring out how to mock the $window object. Here is my interceptor code :
'use strict';
angular.module('Domain.handlers')
.config(function($httpProvider) {
$httpProvider.responseInterceptors.push('UnauthorizedInterceptor');
})
.factory('UnauthorizedInterceptor', function($q, $injector, $window, ENV) {
return function(promise) {
var success = function(response) { return response; };
var error = function(response) {
if (response.status === 401) {
$window.location.href = ENV.account + '/oauth/authorize?client_id=' + ENV.clientId + '&redirect_uri=' + ENV.app + '/oauth/callback&response_type=token';
}
return $q.reject(response);
};
return promise.then(success, error);
};
});
And here is my spec :
'use strict';
describe('Domain.handlers.response', function() {
var UnauthorizedInterceptor,
httpProvider,
$httpBackend,
$http,
token = '123456789';
beforeEach(module('Domain.handlers', function($httpProvider) {
httpProvider = $httpProvider;
}));
beforeEach(inject(function(_UnauthorizedInterceptor_, _$httpBackend_, _$http_) {
UnauthorizedInterceptor = _UnauthorizedInterceptor_;
$httpBackend = _$httpBackend_;
$http = _$http_;
}));
describe('UnauthorizedInterceptor', function() {
it('should be defined', function() {
expect(UnauthorizedInterceptor).toBeDefined();
});
describe('HTTP status', function() {
describe('is 200 OK', function() {
it('should return a 200 status', function() {
$httpBackend.expectGET('http://api.domain.com/clients').respond(200, {});
$http.get('http://api.domain.com/clients');
$httpBackend.flush();
});
});
describe('is 401 Unauthorized', function() {
it('should redirect to accounts.domain.com', inject(function($window) {
$httpBackend.expectGET('http://api.domain.com/clients').respond(401, {});
$http.get('http://api.domain.com/clients');
expect($window.location.href).toEqual('http://accounts.domain.com/oauth/.....');
$httpBackend.flush();
}));
});
});
});
});
I've got a : Expected 'http://localhost:8080/context.html' to equal 'http://accounts.domain.com/oauth/.....'. Any help on how to mock properly the $window object or more generally how to test a 401 + redirection case?

You should structure your interceptor definition using the more recent syntax. Your URL construction should also be in a service so that it can easily be mocked in tests.
.factory('UnauthorizedInterceptor', function($q, $window, OtherService) {
var service = {
responseError: handleUnauthorized
};
return service;
function handleUnauthorized(rejection) {
if (rejection.status === 401) {
$window.location.href = OtherService.getUnauthorizedRedirectURL();
}
return $q.reject(rejection);
}
});
Doing so will let you test it just like any other factory without having to worry about the internal implementations of $http interceptors, or having to mock responses with $httpBackend.
describe('Domain.handlers.response', function() {
var $window,
UnauthorizedInterceptor,
OtherService,
redirectUrl = 'someUrl';
beforeEach(module('Domain.handlers'));
beforeEach(function () {
$window = { location: { href: null } };
module(function($provide) {
$provide.value('$window', $window);
});
});
beforeEach(inject(function(_UnauthorizedInterceptor_, _OtherService_) {
UnauthorizedInterceptor = _UnauthorizedInterceptor_;
OtherService = _OtherService_;
spyOn(OtherService, 'getUnauthorizedRedirectURL').andReturn(redirectUrl);
}));
describe('UnauthorizedInterceptor', function() {
it('should be defined', function() {
expect(UnauthorizedInterceptor).toBeDefined();
});
it('should have a handler for responseError', function () {
expect(angular.isFunction(UnauthorizedInterceptor.responseError)).toBe(true);
});
describe('when HTTP 401', function () {
beforeEach(function () {
var rejection = { status: 401 };
UnauthorizedInterceptor.responseError(rejection);
});
it('should set window location', function () {
expect($window.location.href).toBe(redirectUrl);
});
});
describe('when not HTTP 401', function () {
beforeEach(function () {
var rejection = { status: 500 };
UnauthorizedInterceptor.responseError(rejection);
});
it('should not set window location', function () {
expect($window.location.href).not.toBe(redirectUrl);
});
});
});
});

Here is an example of the responseError interceptor and the corresponding jasmine spec.
angular.module('interceptorDemo').factory('redirectInterceptor', ['$q', '$window', function($q, $window) {
'use strict';
function handleUnauthorizedAccess(config) {
if (401 === config.status) {
$window.location = '/signIn/';
}
return $q.reject(config);
}
return {
responseError: handleUnauthorizedAccess
};
}]);
The interceptor intercepts the ajax request, if the request is failed, then if the status code is 401 then user is redirected to signIn page.
Jasmine spec for the same is:
describe('redirectInterceptor specs', function() {
var redirectInterceptor, $q;
beforeEach(module('interceptorDemo'));
beforeEach(function() {
$window = {
location: {
href: null
}
};
module(function($provide) {
$provide.value('$window', $window);
});
});
beforeEach(inject(function(_redirectInterceptor_, _$q_) {
redirectInterceptor = _redirectInterceptor_;
$q = _$q_;
spyOn($q, 'reject');
}));
describe('redirectInterceptor specs', function() {
it('should redirect to signIn page for unauthorized access', function() {
var response = {
status: 401,
config: {}
};
var promise = redirectInterceptor.responseError(response);
expect($window.location).toBe('/singIn/');
expect($q.reject).toHaveBeenCalled();
});
it('should not redirect to signIn page for error code other than unauthorized access', function() {
var response = {
status: 404,
config: {}
};
var promise = redirectInterceptor.responseError(response);
expect($window.location).toEqual({
href: null
});
expect($q.reject).toHaveBeenCalled();
});
});
});
We have spied on the $q so we can also test that the reject is called for the 401 error.

Related

Jasmine and spy method

angular.module('mine', []).factory('MyFactory', ['$http', '$q', function
MyFactory($http, $q) {
return {
getData: function() {
var deferred = $q.defer(),
url = "http://...";
$http.jsonp(url)
.then(
function(response) {
deferred.resolve(response.data);
},
function(error) {
return $q.reject('Error retrieving data');
}
);
return deferred.promise;
}
};
}
]);
function MyController(MyFactory) {
var self = this;
self.getData = function() {
MyFactory.getData().then(
function(result) {
self.contacts = result;
},
function(error) {
console.log('Error retrieving data: ', error);
}
);
};
}
I am trying to unit test if data from factory go correctly to controller. Here is my unit testing code using Jasmine:
describe('component', () => {
let $componentController,contactsList,ctrl,$q,$rootScope;
beforeEach(angular.mock.module('mine'));
beforeEach(inject((_$componentController_,_MyFactory_, _$q_, _$rootScope_) => {
$componentController = _$componentController_;
ctrl = $componentController('myComponent',null);
$q = _$q_;
contactsList = _MyFactory_;
$rootScope = _$rootScope_;
}));
it('should ... ', function() {
spyOn(contactsList, "getData").and.returnValue(
$q.when({
message: 'awesome message'
}));
ctrl.getData();
$rootScope.$apply();
expect(ctrl.contacts.message).toBe('awesome message');
});
});
However, I am doing something wrong and I am getting the error: I am getting the following error: Possibly unhandled rejection: Error retrieving data thrown.I guess the problem lies on spy method. I'd appreciate any help.

Is it possible to test $resource success and error callbacks in a controller?

I would like to test $resource success and error callbacks in my controller. I don’t want to use $httpBackend as that would be used to test the data service. It seems that there is no way to do it though - the only solution I have found is to use promises instead which I could either resolve or reject. Does this sound right? Anyway, here is what I have at the moment - currently it only tests whether the $resource get() is called:
The controller:
angular
.module('myModule')
.controller('MyCtrl', MyCtrl);
MyCtrl.$inject = [
'dataService'
];
function MyCtrl(
dataService
) {
var vm = this;
vm.getData = getData;
function getData() {
dataService.getData().get(function(response) {
// stuff to test
},
function(error) {
// stuff to test
});
}
The test:
describe('Controller: MyCtrl', function() {
var MyCtrl;
var rootScope;
var scope;
var dataServiceMock = {
getData: jasmine.createSpy('getData')
};
beforeEach(function()
inject(function($controller, $rootScope) {
rootScope = $rootScope;
scope = $rootScope.$new();
MyCtrl = $controller('MyCtrl as vm', {
dataService: dataServiceMock,
});
});
});
describe('vm.getData()', function() {
beforeEach(function() {
dataServiceMock.getData.and.returnValue({
get: jasmine.createSpy('get')
});
});
it('gets the data', function() {
scope.vm.getData();
expect(dataServiceMock.getData().get).toHaveBeenCalled();
});
});
});
Try this
function getData (query) {
var deferred = $q.defer();
var httpPromise = $resource(query,{},{
post:{
method:"GET",
isArray: false,
responseType: "json"
}
});
httpPromise.post({}, {},
function(data) {
try {
var results = {}
results.totalItems = data.response;
deferred.resolve(results);
} catch (error) {
console.log(error.stack);
deferred.reject();
}
},
function(error) {
deferred.reject();
}
);
return deferred.promise;
}

Angular and Jasmine: How to test requestError / rejection in HTTP interceptor?

How would you test this requestError method?
angular.module('myApp').factory 'HTTPInterceptor', [
'$rootScope'
'$q'
'$window'
'LocalStorageService'
'$injector'
($rootScope, $q, $window, $injector) ->
{
request: (config) ->
config.headers = config.headers or {}
// do stuff with config then
config # Return Config
requestError: (rejection) ->
q.reject(rejection) # Return the promise rejection
...
Hey its been long since you post this question but I think I have solution for this question.Last describe is for requestError method in intreceptors. Here is example of how I test my httpInterceptor:
TEST
/* jshint undef:false*/
(function() {
'use strict';
describe('httpInterceptor', function() {
var httpInterceptor, modal, state, q, actualOptions, rootScope, scope, mockAuthenticationService, notification;
beforeEach(module('app'));
beforeEach(inject(function(_httpInterceptor_, $q, $uibModal, $state, $rootScope, _AuthenticationService_, _Notification_) {
httpInterceptor = _httpInterceptor_;
rootScope = $rootScope;
scope = rootScope.$new();
q = $q;
state = $state;
mockAuthenticationService = _AuthenticationService_;
notification = _Notification_;
function FakeModal(){
this.resultDeferred = $q.defer();
this.result = this.resultDeferred.promise;
}
FakeModal.prototype.open = function(options){
actualOptions = options;
return this; };
FakeModal.prototype.close = function (item) {
this.resultDeferred.resolve(item);
rootScope.$apply(); // Propagate promise resolution to 'then' functions using $apply().
};
FakeModal.prototype.dismiss = function (item) {
this.resultDeferred.reject(item);
rootScope.$apply(); // Propagate promise resolution to 'then' functions using $apply().
};
modal = new FakeModal();
}));
describe('httpInterceptor', function() {
beforeEach(function () {
var rejection = { status: 400 , data: {exception: "class org.usda.fns.memsng.exceptions.ReviewTimeoutException"}};
httpInterceptor.responseError(rejection);
});
it('with 400 error status and open idle modal', function () {
rootScope.$apply();
modal.close('close');
});
});
describe('httpInterceptor', function() {
it('with 400 error status and open notification', function () {
var rejection = { status: 400 , data: {exception: "test"}};
httpInterceptor.responseError(rejection);
});
});
describe('httpInterceptor', function() {
beforeEach(function () {
spyOn(state, 'go');
var rejection = { status: 403 };
httpInterceptor.responseError(rejection);
});
it('with 403 error status and go to unauthorized page', function () {
var expectedState = 'root.unauthorized';
expect(state.go).toHaveBeenCalledWith(expectedState);
});
});
describe('httpInterceptor', function() {
beforeEach(function () {
spyOn(state, 'go');
});
it('with requestError method', function () {
var rejection = { status: 403 };
httpInterceptor.requestError(rejection);
});
});
});
})();
Actual Interceptor
function httpInterceptor($q, $log, $injector) {
return {
request: function(config) {
return config;
},
requestError: function(rejection) {
$log.debug(rejection);
return $q.reject(rejection);
},
response: function(response) {
$log.debug('response: ', response);
return response;
},
responseError: function(rejection) {
$log.debug(rejection);
var state = $injector.get('$state');
if (rejection.status === 403) {
state.go('root.unauthorized');
}
return $q.reject(rejection);
}
};
}

How to unit-test angular-toastr using jasmine

This is a function in my controller which uses Toastr for notifications. How would I test Toastr in my Jasmine unit test for this function.
$scope.login = function(user) {
$scope.user = user;
MyAuthService.login($scope.user)
.then(function(response) {
MyConfig.setUser(response.data.data);
toastr.success('Welcome', 'Login!',{closeButton: true});
});
}
As you are using promises you should use $q to mock myAuthService.login to return a resolved promise. You also want to spy on toastr.success and MyConfig.setUser. After calling $scope.login() you need to resolve the resolved promise and then call $rootScope.$digest();:
describe('MyCtrl', function() {
var createController, $scope, $rootScope, myAuthService, myConfig, toastr, deferred;
beforeEach(module('app'));
beforeEach(inject(function($controller, _$rootScope_, $q) {
$rootScope = _$rootScope_;
deferred = $q.defer();
myConfig = {
setUser: function (data) {
}
};
spyOn(myConfig, 'setUser');
myAuthService = {
login: function () {
}
};
spyOn(myAuthService, 'login').and.returnValue(deferred.promise);
toastr = {
success: function (message, title, options) {
}
};
spyOn(toastr, 'success');
$scope = $rootScope.$new();
createController = function() {
return $controller('MyCtrl',
{
$scope: $scope,
MyAuthService: myAuthService,
MyConfig: myConfig,
toastr: toastr
});
};
}));
it('login sets user in config and shows success toastr', function() {
//Arrange
createController();
var response = {
data: {
data: {
username: 'test'
}
}
};
$scope.user = {
username: 'test'
};
//Act
$scope.login();
deferred.resolve(response);
$rootScope.$digest();
//Assert
expect(myAuthService.login).toHaveBeenCalledWith($scope.user);
expect(myConfig.setUser).toHaveBeenCalledWith(response.data.data);
expect(toastr.success).toHaveBeenCalledWith('Welcome', 'Login!', {closeButton: true});
});
});
Plunkr

how we do spyOn with a function referred in a async call

Need help on Jasmine with angularjs :
I created spec for this below angular code. I could able to work with the service, if i don't have a data1 function. I need to know how we do spyOn with a function referred in a async call.
'use strict';
angular.module('myApp.services', [])
.factory("exampleService", function ($http) {
var data1 = function () {
return '/test';
}
return {
data: function () {
return data1();
},
getData: function () {
return $http.get("/exampleUrl" + data1());
}
}
});
Below spec code is
'use strict';
describe('service', function () {
var $httpBackend;
beforeEach(module('myApp.services'));
beforeEach(inject(function ($injector) {
$httpBackend = $injector.get("$httpBackend");
$httpBackend.when("GET", "/exampleUrl/test1")
.respond(200, {
value: "goodValue"
});
}));
afterEach(function () {
$httpBackend.flush();
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('exampleService successful http request', function () {
it('.value should be "goodValue"', inject(function (exampleService) {
spyOn(exampleService, 'data').and.callFake(function () {
//done();
return '/test1';
});
exampleService.getData().success(function (response) {
expect(response.value).toEqual("goodValue");
}).error(function (response) {
//should not error with $httpBackend interceptor 200 status
expect(false).toEqual(true);
});
}));
});
});
But i am getting exception like this
Error : Unexpected Request : GET /exampleUrl/test

Resources