I am trying to do unit test of my angular app with karma. I am getting some error. Am i missing something? A
This my controller
(function () {
"use strict"
angular
.module("myApp")
.controller("userCtrl",['$scope', '$state', 'userService', 'appSettings','md5','currentUser','$rootScope',
function ($scope, $state, userService, appSettings,md5,currentUser, $rootScope) {
$scope.login = function() {
$scope.loading = true;
if($scope.password != null){
var user ={
username:$scope.username,
password:md5.createHash($scope.password)
}
var getData = userService.login(user);
getData.then(function (response) {
console.log(response);
$scope.loading = false;
currentUser.setProfile(user.username, response.data.sessionId);
$state.go('videos');
}, function (response) {
console.log(response.data);
});
}else{
$scope.msg = "Password field is empty!"
}
}
}])
}());
This is my test codes
'use strict';
describe('userCtrl', function() {
beforeEach(module('myApp'));
var scope, userCtrl, apiService,q, deferred, currentUser;
describe('$scope.login', function(){
beforeEach(function(){
apiService = {
login: function () {
deferred = q.defer();
return deferred.promise;
};
};
});
beforeEach(inject(function($controller, $rootScope, $q, _currentUser_){
var user ={name:'ali',password:'password'};
scope = $rootScope.$new();
q = $q;
// The injector unwraps the underscores (_) from around the parameter names when matching
userCtrl = $controller('userCtrl', {
$scope:scope,
userService:apiService
});
//userService = _userService_;
currentUser = _currentUser_;
}));
it('should call user service login', function() {
spyOn(apiService, 'login').and.callThrough();
scope.login();
deferred.resolve(user);
expect(apiService.login).toHaveBeenCalled();
});
it('checks the password field', function() {
scope.login();
expect(scope.msg).toEqual('Password field is empty!');
});
});
});
And i am getting this error
enter image description here
If you have to test controller then use to spyon for service method and in case of service then use HttpBackend
describe('Testing a Controller that uses a Promise', function() {
var $scope;
var $q;
var deferred;
beforeEach(module('search'));
beforeEach(inject(function($controller, _$rootScope_, _$q_, searchService) {
$q = _$q_;
$scope = _$rootScope_.$new();
// We use the $q service to create a mock instance of defer
deferred = _$q_.defer();
// Use a Jasmine Spy to return the deferred promise
spyOn(searchService, 'search').and.returnValue(deferred.promise);
// Init the controller, passing our spy service instance
$controller('SearchController', {
$scope: $scope,
searchService: searchService
});
}));
it('should resolve promise', function() {
// Setup the data we wish to return for the .then function in the controller
deferred.resolve([{
id: 1
}, {
id: 2
}]);
// We have to call apply for this to work
$scope.$apply();
// Since we called apply, not we can perform our assertions
expect($scope.results).not.toBe(undefined);
expect($scope.error).toBe(undefined);
});
});
This for same using spyon for service method then use $appy method to make it work.
Related
I'm trying to test a method that deletes an item from a list after user confirmation.
Controller:
app.controller('mainCtrl', ['$scope', '$window', 'dataService', function($scope, $window, dataService) {
var vm = this;
vm.delete = function(id, index) {
if($window.confirm('Are you sure?')) {
dataService.deleteById(id).then(function() {
vm.list.splice(index, 1)
});
}
};
}]);
Sevice:
app.service('dataService', ['$http', function($http) {
this.deleteById = function(id) {
return $http.delete('delete-item?id=' + id);
};
}]);
Test:
describe('Testing RecipesController', function() {
var scope, ctrl, dataServiceMock, q, deferred, window;
beforeEach(function() {
dataServiceMock = {
deleteById: function() {
deferred = q.defer();
return deferred.promise;
}
};
});
beforeEach(function() {
module('app');
inject(function($rootScope, $controller, $q, $window) {
q = $q;
window = $window;
scope = $rootScope.$new();
ctrl = $controller('mainCtrl', {
$scope: scope,
dataService: dataServiceMock
});
});
});
it('should delete recipe if the user clicked "OK"', function() {
spyOn(window, 'confirm').and.returnValue(true);
spyOn(dataServiceMock, 'deleteById').and.callThrough();
var item= {
id: 2,
name: 'Shirt'
};
ctrl.list = ['Hat', 'Shirt'];
ctrl.delete(item, 1);
expect(dataServiceMock.deleteById).toHaveBeenCalled();
expect(ctrl.list.length).toBe(1);
});
});
I successfully mocked the confirm dialog and the delete method, and the test to check if the method been called even passes.
But, The promise.then() isn't working.
After I run the test I got this message "Expected 2 to be 1".
I see one thing for sure, which is that you never resolve or reject your promise in the data service mock. Try changing the mock to this:
beforeEach(function() {
dataServiceMock = {
deleteById: function() {
deferred = q.defer();
deferred.resolve({ /* whatever data you want to resolve with */ });
return deferred.promise;
// You could also shorten this whole mock function to just:
// return $q.resolve({ /* some data */ });
}
};
});
Also, don't forget to execute the $digest() function on the $rootScope at the end of your test... you're actually executing it on your controller's scope, NOT the root scope.
Hold onto the actual $rootScope object - change your beforeEach to:
var $rScope;
beforeEach(function() {
module('app');
inject(function($rootScope, $controller, $q, $window) {
q = $q;
window = $window;
$rScope = $rootScope;
ctrl = $controller('mainCtrl', {
$scope: $rootScope.$new(),
dataService: dataServiceMock
});
});
});
Then in your test, execute $digest on the root scope at the end:
it('should delete recipe if the user clicked "OK"', function() {
// all your test codez...
$rScope.$digest();
});
i have a problem when mocking a data service promise.
I use AngularJS and Jasmine 2.2.0
My Code is:
Controller
app.controller('homeController', ['$scope', 'ngAppSettings', 'homeViewModel', 'storageService', 'homeService',
function ($scope, ngAppSettings, homeViewModel, storageService, homeService) {
$scope.applicationName = ngAppSettings.applicationName;
$scope.model = homeViewModel;
storageService.getStorageAuth().then(function (data) {
$scope.model.userName= data.name;
}, function (err) {
alert(err.errors[0].message);
});
}]);
Service
app.service('storageService', ['$q', 'ngAppSettings', 'localStorageService',
function ($q, ngAppSettings, localStorageService) {
this.getStorageAuth = function () {
var deferred = $q.defer();
var userAuth = localStorageService.get(ngAppSettings.storageUser);
if (userAuth) {
data = userAuth;
}
deferred.resolve(data);
return deferred.promise;
};
}]);
Test Spec
describe('Controllers: homeController', function () {
var $rootScope;
var $scope;
var ctrl;
var $q;
var deferred;
var storageService;
beforeEach(module('IspFrontEndTemplateApp'));
beforeEach(inject(function (_$q_, _storageService_) {
$q = _$q_;
storageService = _storageService_;
deferred = $q.defer();
spyOn(storageService, "getStorageAuth").and.returnValue(deferred.promise);
}));
beforeEach(inject(function (_$rootScope_, $controller) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
ctrl = $controller('homeController', { $scope: $scope });
}));
it('UserName is: UsuarioTeste', function () {
deferred.resolve({
isAuth: true,
userName: "UsuarioTeste",
name: "UsuarioTeste"
});
expect($scope.model.userName).toBe('UsuarioTeste');
});
});
The error is: Expected '' to be 'UsuarioTeste'.
I need test my model properties in controller but the value is not refreshed
You need to $digest one more time in your test:
$scope.$digest();
expect($scope.model.userName).toBe('UsuarioTeste'); // will work
The reason is that promises are resolved asynchronously in the sense that a promise never calls its success or error callbacks within the same $apply it has been created in, even if it has been resolved. Code could be hard to read if you create a promise in the beginning of your function and the effect of the resolved promise would be already observable a few lines later.
I have the following in my Angular.js controller
function alreadyRegistered() {
if(reg.regForm.email.$valid){
console.log('this is logged in my console.');
authFactory.doesUserExist(reg.user)
.then(function(response){
console.log('this line if never hit');
if(response) {
reg.regForm.email.$setValidity('userExists', false);
} else {
reg.regForm.email.$setValidity('userExists', true);
}
})
.catch(function(err){
reg.error = err;
});
} else {
reg.regForm.email.$setValidity('userExists', true); // Remove userExists validation error.
}
};
I would like to test that the validation is working correctly by setting the response inside the .then() to either true or false. However I can not get my test to go inside the .then().
Here's my test:
describe('Registration Controller Tests', function() {
var $controller, $scope, defer, doesUserExistSpy, authFactory, Registration,
beforeEach(module('enigma'));
beforeEach(inject(function (_$controller_, _$rootScope_, $q, $injector) {
$controller = _$controller_;
$scope = _$rootScope_;
defer = $q.defer();
// Create spies
doesUserExistSpy = jasmine.createSpy('doesUserExist').and.returnValue(defer.promise);
authFactory = {
register: registerSpy,
doesUserExist: doesUserExistSpy
};
// Init register controller with mocked services
Registration = $controller('Registration', {
$scope: $scope,
authFactory: authFactory,
$state: $state
});
// digest to update controller with services and scope
$scope.$digest();
}));
describe('check email field validity', function () {
var element, regForm;
beforeEach(inject(function ($rootScope, $compile) {
$scope = $rootScope;
element = angular.element(
'<form name="regForm">' +
'<input type="email" ng-model="test.email" name="email" value="bwayne#wayneenterprise.com" />' +
'</form>'
);
$compile(element)($scope);
regForm = $scope.regForm;
}));
it('should set regForm.email.$error.userExists to true if /doesUserExist returns true', function () {
$httpBackend.whenPOST('/doesUserExist').respond(defer.resolve(true)); // I'm trying to set the value for 'response' in the .then() for the controller.
Registration.alreadyRegistered();
$scope.$digest();
expect(regForm.email.$error.userExists).toEqual(true);
});
});
});
Here's my authFactory code:
angular
.module('enigma.authFactory', [])
.factory('authFactory', authFactory);
authFactory.$inject = ['$http', '$q'];
function authFactory($window, $http, $q, sessionStorageFactory){
var auth = {
doesUserExist: doesUserExist
};
return auth;
function doesUserExist(email){
var defered = $q.defer();
$http.post('/app/doesUserExist', email)
.success(function(data){
if(data.message !== 'user exists'){
defered.resolve(false);
} else {
defered.resolve(true);
}
});
return defered.promise;
}
}
Seeing as you are completely mocking out your authFactory service, you don't need to touch $httpBackend. Simply resolve the defer object that is returned by your mock. For example
it('should set regForm.email.$error.userExists to true if /doesUserExist returns true', function () {
Registration.alreadyRegistered();
expect(doesUserExistSpy).toHaveBeenCalled();
defer.resolve(true);
$scope.$apply();
expect(regForm.email.$error.userExists).toEqual(true);
});
'use strict'
webApp.controller 'NavigationController', [
'$scope'
'$rootScope'
'UserService'
($scope, $rootScope, UserService) ->
$scope.init = ->
UserService.isAuthenticated().then (authenticated) ->
$scope.isAuthenticated = authenticated
$scope.init()
]
I want to write a test to spyOn if isAuthenticated was called from UserService. In my beforeEach, I have:
beforeEach ->
module 'webApp'
inject ($injector) ->
$httpBackend = $injector.get '$httpBackend'
$q = $injector.get '$q'
$rootScope = $injector.get '$rootScope'
$scope = $rootScope.$new()
$controller = $injector.get '$controller'
UserServiceMock =
isAuthenticated: ->
deferred = $q.defer()
deferred.promise
controller = $controller 'AboutUsController',
'$scope': $scope
'$rootScope': $rootScope
'UserService': UserServiceMock
$httpBackend.whenGET('/api/v1/session').respond 200
Any help would be appreciated.. thanks
You can just set a variable to true when isAuthenticated is called in your UserServiceMock. e.g.:
var isAuthenticatedCalled;
var controller;
beforeEach(function() {
isAuthenticatedCalled = false;
module('webApp');
inject(function($injector) {
//...
UserServiceMock = {
isAuthenticated: function() {
isAuthenticatedCalled = true;
var deferred = $q.defer();
deferred.resolve();
return deferred.promise;
}
};
controller = $controller('AboutUsController', {
'$scope': $scope,
'$rootScope': $rootScope,
'UserService': UserServiceMock
});
// ...
});
});
it('should call isAuthenticated', function() {
expect(isAuthenticatedCalled).toBe(true)
});
Alternatively you could use Jasmine's spyOn function.
UserServiceMock = {
isAuthenticated: function() {
var deferred = $q.defer();
deferred.resolve();
return deferred.promise;
}
};
spyOn(UserServiceMock, 'isAuthenticated');
And in your test you can do
it('should call isAuthenticated', function() {
expect(UserServiceMock.isAuthenticated).toHaveBeenCalled()
});
My controller is:
angularMoonApp.controller('SourceController', ['$scope', '$rootScope', '$routeParams', 'fileService', function ($scope, $rootScope, $routeParams, fileService) {
$scope.init = function() {
$rootScope.currentItem = 'source';
fileService.getContents($routeParams.path).then(function(response) {
$scope.contents = response.data;
$scope.fileContents = null;
if(_.isArray($scope.contents)) {
// We have a listing of files
$scope.breadcrumbPath = response.data[0].path.split('/');
} else {
// We have one file
$scope.breadcrumbPath = response.data.path.split('/');
$scope.breadcrumbPath.push('');
$scope.fileContents = atob(response.data.content);
fileService.getCommits(response.data.path).then(function(response) {
$scope.commits = response.data;
});
}
});
}
$scope.init();
}]);
My test is pretty simple:
(function() {
describe('SourceController', function() {
var $scope, $rootScope, $httpBackend, createController;
beforeEach(module('angularMoon'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
var $controller = $injector.get('$controller');
createController = function() {
return $controller('SourceController', {
'$scope': $scope
});
};
}));
it("should set the current menu item to 'source'", function() {
createController();
$scope.init();
expect($rootScope.currentItem).toBe('source');
});
it("should get the contents of the root folder", function() {
createController();
$scope.init();
// NOT SURE WHAT TO DO HERE!
});
});
})();
I want to test that the fileService had it's getContents function called and mock a response so that I can test the two scenarios (if is array and if isn't`)
I would recommend using Jasmine spies for this.
Here is an example that might help. I usually put the spyOn call in the beforeEach.
var mockedResponse = {};
spyOn(fileService, "getContents").andReturn(mockedResponse);
In the 'it' part:
expect(fileService.getContents).toHaveBeenCalled();
To get the response, just call the method in your controller that calls the fileService method. You may need to manually run a digest cycle too. Snippet from one of my tests:
var testOrgs = [];
beforeEach(inject(function(coresvc) {
deferred.resolve(testOrgs);
spyOn(coresvc, 'getOrganizations').andReturn(deferred.promise);
scope.getAllOrganizations();
scope.$digest();
}));
it("getOrganizations() test the spy call", inject(function(coresvc) {
expect(coresvc.getOrganizations).toHaveBeenCalled();
}));
it("$scope.organizations should be populated", function() {
expect(scope.allOrganizations).toEqual(testOrgs);
expect(scope.allOrganizations.length).toEqual(0);
});
deferred in this case is a promise created with $q.defer();
You can create a spy and verify only that fileService.getContents is called, or either verify extra calls (like promise resolution) by making the spy call through. Probably you should also interact with httpBackend since you may need to flush the http service (even though you use the mock service).
(function() {
describe('SourceController', function() {
var $scope, $rootScope, $httpBackend, createController, fileService;
beforeEach(module('angularMoon'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
// See here
fileService = $injector.get('fileService');
spyOn(fileService, 'getContents').andCallThrough();
var $controller = $injector.get('$controller');
createController = function() {
return $controller('SourceController', {
'$scope': $scope
'fileService': fileService
});
};
}));
it("should get the contents of the root folder", function() {
createController();
$scope.init();
expect(fileService.getContents).toHaveBeenCalled();
});
});
})();
You can also add expectations to what happens inside the callback but you should issue a httpBackend.flush() before.