How to unit-test angular-toastr using jasmine - angularjs

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

Related

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

Stubbing an angularJs service with a function that returns a promise using sinon

I'm trying to test a controller that calls a method on a service. The service method returns a promise, and the controller immediately invokes .then() inline after calling the service method. I'm trying to stub the service using sinon and Jasmine keeps throwing an error saying that then is undefined and not a function.
Here is the controller:
var loginModalController = function ($scope, authenticationService) {
this.submit = submit;
function submit(user, password) {
$scope.email = user;
authenticationService.login(user, password)
.then(handleSuccessLogin, handleErrorLogin);
}
}
Here is the service:
function authenticationService($http, $q, endPointService) {
var baseUri = endPointService.getApiEndpoint();
var service = {
getTermsAndConditions: getTermsAndConditions,
login: login,
acceptTerms: acceptTerms
};
return service;
function getTermsAndConditions() {
...
};
function login(user, password) {
var deferred = $q.defer();
$http({ method: 'POST', url: baseUri + '/api/tokens', data: { username: user, password: password } }).
success(function (data, status, headers, config) {
$http.defaults.headers.common.Authorization = 'Basic ' + data.EncryptedTokenId;
deferred.resolve(data);
}).
error(function (data, status, headers, config) {
deferred.reject(status);
});
return deferred.promise;
};
function acceptTerms() {
...
};
}
And here is the test:
describe('loginModalController', function () {
var scope, loginModalController, authenticationServiceMock, localSaverServiceMock;
var loginInformationMock = { 'firstName': 'Testuser' };
beforeEach(function () {
module('clientAppModule');
inject(function ($rootScope, $controller, authenticationService, localSaverService) {
scope = $rootScope.$new();
authenticationServiceMock = sinon.stub(authenticationService)
.login.returns({ then: function () { return loginInformationMock } });
localSaverServiceMock = sinon.stub(localSaverService);
loginModalController = $controller('loginModalController', {
$scope: scope,
$state: {},
authenticationService: authenticationServiceMock,
errorCodes: {},
localSaverService: localSaverServiceMock
});
});
});
it('should login', function () {
loginModalController.submit("test", "test");
});
});
Four issues with my code:
I was unnecessarily using Sinon
I was using the return value of stub() rather than just letting it stub the service.
I wasn't using $q to return a deferred promise to match the login function.
I needed to call $digest() on the scope to get the deferred promise to resolve before asserting.
So here is the fixed test code:
beforeEach(module('clientAppModule'));
describe('loginModalController', function () {
var scope, authenticationService, localSaverService;
var loginInformationMock = { 'firstName': 'Testuser' };
beforeEach(inject(function ($injector, $rootScope, $controller, $q) {
scope = $rootScope.$new();
scope.$close = function () { };
authenticationService = $injector.get('authenticationService');
localSaverService = $injector.get('localSaverService');
spyOn(authenticationService, 'login').and.callFake(function () {
var deferred = $q.defer();
deferred.resolve(loginInformationMock);
return deferred.promise;
});
spyOn(localSaverService, 'saveLoginInformation').and.stub();
$controller('loginModalController', {
$scope: scope,
$rootScope: {},
$state: {},
authenticationService: authenticationService,
errorCodes: {},
localSaverService: localSaverService
});
}));
it('should call login on authenticationService', function () {
// Arrange
// Act
scope.submit("test", "test");
// Assert
expect(authenticationService.login).toHaveBeenCalled();
});
it('should save login info after successful login', function () {
// Arrange
// Act
scope.submit("test", "test");
scope.$digest();
// Assert
expect(localSaverService.saveLoginInformation).toHaveBeenCalled();
});
});

Unit test controller with injected service

I want to test that my injected service is being called in my controller.
login.controller.js
angular.module('exampleModule')
.controller('LoginCtrl', ['$state', 'AuthService',
function($state, AuthService) {
var self = this;
self.submit = function() {
AuthService.login(self.credentials)
.then(function(res) {
console.log('success');
$state.go('home');
}, function(res) {
if (res.status === 400) {
console.log('error')
}
});
};
}
]);
login.service.js
angular.module('exampleModule')
.factory('AuthService', ['$http',
function($http) {
var authService = {};
authService.login = function(credentials) {
return $http.post('/api/authenticate', credentials);
.then(function(res) {
return res;
});
};
return authService;
}
]);
login.controller.test.js
describe('Controller: LoginCtrl', function() {
beforeEach(module('exampleModule'));
var ctrl, authService;
beforeEach(inject(function($controller, AuthService){
ctrl = $controller('LoginCtrl');
authService = AuthService;
}));
describe('submit function', function() {
beforeEach(function(){
ctrl.submit();
});
it('should call AuthService', function() {
expect(authService.login).toHaveBeenCalled();
});
});
});
How do I properly test whether AuthService.login was called? With the way I'm injecting the AuthService into my test, I'm getting these errors:
TypeError: 'undefined' is not an object (evaluating 'AuthService.login(self.credentials).then')
You need to mock the login() method and make it return a promise:
describe('Controller: LoginCtrl', function() {
beforeEach(module('exampleModule'));
var ctrl, authService, $q;
beforeEach(inject(function($controller, _$q_, AuthService){
ctrl = $controller('LoginCtrl');
$q = _$q_;
authService = AuthService;
}));
describe('submit function', function() {
beforeEach(function(){
var deferred = $q.defer();
spyOn(authService, 'login').and.returnValue(deferred.promise);
ctrl.submit();
});
it('should call AuthService', function() {
expect(authService.login).toHaveBeenCalled();
});
});
});
Working Plunker

Mocking HTTP service unit test with AngularJS and Jasmine

I am attempting to build a mock service so that my unit tests can verify certain functions are called and updated accordingly. Unfortunately I cannot get this to work.
Im currently getting an error undefined is not a function on this line:
spyOn(statusService, 'getModuleStatus').andCallThrough();
My actual service looks like this:
serviceStatusServices.factory('serviceStatusAppAPIservice', function ($http) {
var serviceStatusAppAPI = {};
serviceStatusAppAPI.getModuleStatus = function () {
return $http({
method: 'JSON',
url: '/settings/getservicestatusandconfiguration'
});
}
serviceStatusAppAPI.setModuleStatus = function (module) {
return $http({
method: 'POST',
url: '/settings/setservicestatusandconfiguration',
data: { moduleId: module.ModuleId, configData: module.ConfigValues }
});
}
return serviceStatusAppAPI;
});
My update function
serviceStatusControllers.controller('serviceStatusController', ['$scope', 'serviceStatusAppAPIservice', '$filter', '$timeout', function ($scope, serviceStatusAppAPIservice, $filter, $timeout) {
$scope.update = function () {
$scope.loading = true;
serviceStatusAppAPIservice.getModuleStatus().then(function (response) {
$scope.modules = $filter('orderBy')(response.data.moduleData, 'ModuleName')
...
My tests look like this
describe('ServiceStatusController', function () {
beforeEach(module("serviceStatusApp"));
var scope;
var statusService;
var controller;
var q;
var deferred;
// define the mock people service
beforeEach(function () {
statusService = {
getModuleStatus: function () {
deferred = q.defer();
return deferred.promise;
}
};
});
// inject the required services and instantiate the controller
beforeEach(inject(function ($rootScope, $controller, $q) {
scope = $rootScope.$new();
q = $q;
controller = $controller('serviceStatusController', {
$scope: scope, serviceStatusAppAPIservice: statusService });
}));
describe("$scope.update", function () {
it("Updates screen", function () {
spyOn(statusService, 'getModuleStatus').andCallThrough();
scope.update();
deferred.resolve();
expect(statusService.getModuleStatus).toHaveBeenCalled();
expect(scope.modules).not.toBe([]);
});
});
});
Also, how do I pass any mock data returned from the service to the caller. Currently in my model I do serviceStatusAppAPI.getModuleStatus(data) then use data.Data to get out the returned JSON.
I assume if you are doing something like this in your ctrl
scope.update = function() {
serviceStatusAppAPIservice.setModuleStatus(url).then(function (data) {
scope.modules = data;
})
};
Service which returns promise
.factory('serviceStatusAppAPI', function($http, $q) {
return {
getModuleStatus: function() {
var defer = $q.defer();
$http({method: 'GET', url: '/settings/getservicestatusandconfiguration'})
.success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
defer.resolve(data);
})
.error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
window.data = data;
});
return defer.promise;
}
};
});
So in you controller you will get data like this
serviceStatusAppAPI.getModuleStatus().then(function (data) {
$scope.modules = $filter('orderBy')(data.moduleData, 'ModuleName')
})
This is how you can run your unit test case
beforeEach(function() {
var statusService = {};
module('myApp', function($provide) {
$provide.value('serviceStatusAppAPIservice', statusService);
});
statusService.modalStatus = {
moduleData: [{ModuleName: 'abc'}, {ModuleName: 'def'}]
};
inject(function ($q) {
statusService.setModuleStatus = function () {
var defer = $q.defer();
defer.resolve(this.modalStatus);
return defer.promise;
};
statusService.getModuleStatus = function () {
var defer = $q.defer();
defer.resolve(this.modalStatus);
return defer.promise;
};
});
});
beforeEach(inject(function ($rootScope, $controller, _$stateParams_) {
scope = $rootScope.$new();
stateParams = _$stateParams_;
controller = $controller;
}));
var myCtrl = function() {
return controller('ServiceStatusController', {
$scope: scope,
});
};
it('should load status', function () {
myCtrl();
scope.update();
scope.$digest();
expect(scope.modules).toBe({
status: 'active'
});
});

Angular Jasmine test response interceptor

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.

Resources