I'm fairly new to Javascript and just learning AngularJS but I've gotten most of my test cases to work with some great examples I've found. Unfortunately I can't seem to find anything to help me test my current case.
I'm testing a Controller using a mocked Service whose method returns a promise. I would like the mocked Service to return an error in order to execute the '.catch' block in the controller method. I can tell that it's not getting called correctly in a couple of ways:
The controller under test, specifically need to test the '.catch' in $scope.login:
login.js
'use strict';
angular.module('ibcwebDashApp')
.controller('LoginCtrl', function ($scope, Auth, $location) {
$scope.user = {};
$scope.errors = {};
$scope.login = function(form) {
$scope.submitted = true;
if(form.$valid) {
Auth.login({
email: $scope.user.email,
password: $scope.user.password
})
.then( function() {
// Logged in, redirect to home
$location.path('/');
})
.catch( function(err) {
err = err.data;
$scope.errors.other = err.message;
});
}
};
});
The service and method I'm attempting to mock:
Auth.login
'use strict';
angular.module('ibcwebDashApp')
.factory('Auth', function Auth($location, $rootScope, Session, User, $cookieStore) {
// Get currentUser from cookie
$rootScope.currentUser = $cookieStore.get('user') || null;
$cookieStore.remove('user');
return {
/**
* Authenticate user
*
* @param {Object} user - login info
* @param {Function} callback - optional
* @return {Promise}
*/
login: function(user, callback) {
var cb = callback || angular.noop;
return Session.save({
email: user.email,
password: user.password
}, function(user) {
$rootScope.currentUser = user;
return cb();
}, function(err) {
return cb(err);
}).$promise;
},
And finally, my test file. The funny part is that all tests are passing but the 'expect' in the last test can be changed to pretty much anything and it still passes. The first two tests seem to run as expected but the last test is where I'm trying to execute the catch block by throwing an error from the mock Auth service:
login.unit.js
'use strict';
describe('Controller: LoginCtrl', function () {
var $scope, $location, loginCtrl, mockAuthService;
beforeEach(function() {
mockAuthService = jasmine.createSpyObj('Auth', ['login']);
module('ibcwebDashApp');
module(function($provide) {
$provide.value('Auth', mockAuthService);
});
inject(function($rootScope, $controller, $q, _$location_) {
//create an empty scope
$scope = $rootScope.$new();
$location = _$location_;
//declare the controller and inject our empty scope
loginCtrl = $controller('LoginCtrl', {$scope: $scope, Auth: mockAuthService});
});
});
describe('successful login', function() {
beforeEach(function() {
inject(function($q) {
mockAuthService.login.andReturn($q.when());
});
});
it('should call auth.login with the scope email and password when form is valid', function() {
//given
$scope.form = {};
$scope.form.$valid = true;
$scope.user.email = '[email protected]';
$scope.user.password = 'password123';
//when
$scope.login($scope.form);
//then
expect($scope.submitted).toBe(true);
expect(mockAuthService.login).toHaveBeenCalledWith({email:'[email protected]', password:'password123'});
$scope.$apply(); //force return of auth.login promise
expect($location.path()).toBe('/');
});
it('should not call auth.login if form is invalid', function() {
//given
$scope.form = {};
$scope.form.$valid = false;
//when
$scope.login($scope.form);
expect(mockAuthService.login).not.toHaveBeenCalled();
});
});
describe('unsuccessful login', function() {
beforeEach(function () {
inject(function () {
mockAuthService.login.andReturn($q.when(new Error('Bad Login!')));
});
it('should set errors.other to the returned auth error message', function() {
//given
$scope.form = {};
$scope.form.$valid = true;
//when
$scope.login($scope.form);
$scope.$apply();
//then
expect($scope.errors.other).toEqual('Bad Login!');
});
});
});
});
I apologize for posting so much code but I wanted to provide as much context as possible. I really appreciate anyone who can help me out as I learn my way around unit testing Angular and promises! Thanks!!!
**UPDATE**
I was able to solve my issue with some help from below and discovering some syntactic errors. Here's what fixed this:
beforeEach
on the last test was not closed properly and actually enclosed the last test causing it not to run correctly (or maybe at all). This is why changing the expect conditions resulted in no errors.beforeEach
inject to: mockAuthService.login.andReturn($q.reject({data: {message: 'Bad Login!'}}));
using the reject
suggested below.beforeEach
I got an error message that $q was not defined so I had to added it to inject(function($q)
Once I corrected these issues the promise was correctly rejected and the error was caught by the appropriate code in the controller.
Before or while running your test, mock out part of the environment like this:
var originalAuthLogin = Auth.login;
Auth.login = function() {
return Promise.reject({data: {message: 'Error message'}});
};
After the test restore the environment to sanity:
Auth.login = originalAuthLogin;
This immediately calls the .catch()
block of the code you're trying to test.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With