Unit test controller with injected service - angularjs

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

Related

Angular/Jasmin promise unit test not working

The following Angular controller works fine:
angular.module("mymodule", [])
.service('svc', function($q) {
this.call = function() {
return $q.when(3);
};
})
.controller('MainCtrl', function($scope,svc) {
svc.call()
.then(function successCallback(response) {
$scope.var1 = response;
console.log($scope.var1);
});
});
However the related Jasmin unit test returns an error:
describe('Testing a Controller that uses a Promise', function () {
var $scope;
var $q;
var deferred;
beforeEach(module('mymodule'));
beforeEach(inject(function($controller, _$rootScope_, _$q_, svc) {
$q = _$q_;
$scope = _$rootScope_.$new();
deferred = _$q_.defer();
spyOn(svc, 'call').and.returnValue(deferred.promise);
$controller('MainCtrl', {
$scope: $scope,
svc: svc
});
}));
it('should resolve promise', function () {
deferred.resolve(11);
$scope.$apply();
expect($scope.var1).toBe(11);
});
The error:
TypeError: undefined is not a constructor (evaluating 'svc.call()
.then(function successCallback(response) {
$scope.var1 = response;
console.log($scope.var1);
})') in test/cookbook2.js (line 11)
Any ideas how to fix this problem?
PLUNK: http://plnkr.co/edit/j8J8J31ver1IE3fl6GyY?p=preview

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 unit testing promises

I am new to promises and seem to have the actual code working, but trying to write a unit test too...
Heres my controller:
appModule.controller('MyController', function($scope, myService, $timeout, mySecondService) {
$scope.accept = false;
$scope.loading = false;
$scope.text = 'Some text';
$scope.testCall = function() {
$scope.person = true;
$scope.text = '';
$scope.loading = true;
myService.getWeather()
.then(function(data) {
if (data.valid===true) {
$timeout(function(){
$scope.loading = false;
$scope.accept = true;
$timeout(function(){
$scope.customer.cart = false;
}, 1400);
}, 1500);
}
else {
$scope.person = false;
$scope.newMessage = mySecondService.getItem();
}
}, function(error) {
$scope.person = false;
$scope.newMessage = mySecondService.getItem();
});
}
});
ControllerSpec:
beforeEach(module('myApp'));
describe("MyController Tests", function() {
var scope;
var ctrl;
var customerInfo = {
"ID" : "212143513",
"firstName" : "Lewis",
"lastName" : "Noel"
};
beforeEach(inject(function($rootScope, $controller, myService) {
scope = $rootScope.$new();
rootScope = $rootScope;
mockMyService = myService;
ctrl = $controller('MyController', {$scope:scope, myService:mockMyService});
spyOn(scope, 'testCall').and.callThrough();
}));
it("On controller load, initialise variables", function() {
expect(scope.text).toEqual('Some text');
expect(scope.loading).toEqual(false);
expect(scope.accept).toEqual(false);
});
});
The above tests the initailisation of the $scope, but nothing within the service...
Here's my service:
appModule.factory('myService', function ($http, $q) {
return {
getWeather: function() {
return $http.get('/cart/customerCart/546546')
.then(function(response) {
if (typeof response.data === 'object') {
return response.data;
}
else {
// invalid response
return $q.reject(response.data);
}
}, function(response) {
// something went wrong
return $q.reject(response.data);
});
}
};
});
If you're asking how to test services in general, here's what I do:
describe("myService", function() {
var myService;
beforeEach(inject(function($rootScope, _myService_) {
myService = _myService_;
}));
it("On controller load, initialise variables", function() {
var result;
myService.getWeather().then( function (data) {
result = data;
});
$rootScope.$apply();
expect(result).toEqual(whatever);
});
});
Notes:
You can use the _myService_ underscore trick to get the service myService without it creating a local variable with that name (see docs).
You need to call $rootScope.$apply() to flush promises in tests. see docs
EDIT:
You're using promises wrongly, I would refactor your factory as so (untested):
appModule.factory('myService', function ($http, $q) {
return {
getWeather: function() {
var defer = $q.defer();
$http.get('/cart/customerCart/546546')
.then(function(response) {
if (typeof response.data === 'object') {
defer.resolve(response.data);
}
else {
// invalid response
defer.reject(response.data);
}
}, function(response) {
// something went wrong
defer.reject(response.data);
});
return defer.promise;
}
};
});

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

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

Resources