I have a promise in a controller that I'm trying to test and I'm getting Error: Unexpected request: POST /v1/users.
I'm trying to spyOn the AuthService.changePassword which returns a promise and test whether it got called or not. Not sure why it's actually making the POST call...
controller
angular.module('example')
.controller('ChangePasswordCtrl', ['AuthService', '$state',
function(AuthService, $state) {
var vm = this;
vm.submitted = false;
vm.submit = function(valid) {
vm.submitted = true;
if (!valid) return false;
AuthService.changePassword(vm.email)
.then(function(res) {
$state.go('reset.confirmation');
}, function(err) {
vm.hasError = true;
});
};
}
]);
unit test
describe('ChangePasswordCtrl', function() {
var ctrl, scope, AuthService, $q, $state, deferred;
beforeEach(module('example'));
function _inject() {
inject(function($controller, $rootScope, _AuthService_, _$state_, _$q_) {
scope = $rootScope.$new();
$state = _$state_;
$q = _$q_;
AuthService = _AuthService_;
ctrl = $controller('ChangePasswordCtrl', {
$scope: scope
});
});
}
describe('#submit', function() {
beforeEach(function() {
_inject();
deferred = $q.defer();
spyOn(AuthService, 'changePassword').and.returnValue(deferred.promise);
spyOn($state, 'go');
});
describe('when email address is valid', function() {
it('should call the changePassword method on the AuthService', function() {
ctrl.submit(true);
scope.$digest();
expect(ctrl.submitted).toBe(true);
expect(AuthService.changePassword).toHaveBeenCalled();
});
});
});
});
Your spec code works for me (the real implementation of AuthService.changePassword doesn't get called): http://jsfiddle.net/7W2XB/7/
angular.module('example', [])
.factory('AuthService', function() {
return {
changePassword: function() {
throw new Error('Should not be called');
}
};
})
.controller('ChangePasswordCtrl', ['AuthService',
function(AuthService) {
var vm = this;
vm.submitted = false;
vm.submit = function(valid) {
vm.submitted = true;
if (!valid) return false;
AuthService.changePassword(vm.email)
.then(function(res) {
$state.go('reset.confirmation');
}, function(err) {
vm.hasError = true;
});
};
}
]);
describe('ChangePasswordCtrl', function() {
var ctrl, scope, AuthService, $q, deferred;
function _inject() {
module('ui.router');
module('example');
inject(function($controller, $rootScope, _AuthService_, _$state_, _$q_) {
scope = $rootScope.$new();
$state = _$state_;
$q = _$q_;
AuthService = _AuthService_;
ctrl = $controller('ChangePasswordCtrl', {
$scope: scope
});
});
}
describe('#submit', function() {
beforeEach(function() {
_inject();
deferred = $q.defer();
spyOn(AuthService, 'changePassword').and.returnValue(deferred.promise);
});
describe('when email address is valid', function() {
it('should call the changePassword method on the AuthService', function() {
ctrl.submit(true);
scope.$digest();
expect(ctrl.submitted).toBe(true);
expect(AuthService.changePassword).toHaveBeenCalled();
});
});
});
});
Some questions that might help make the JSFiddle more realistic to your situation: What versions of angular and Jasmine are you using? - How are you defining the AuthService (presumably using angular.factory)?
Related
I'm new to AngularJS and unit testing,
I'm testing a list that gets changing by selected category.
The test is passing but only if I use the httpBackend.expectGET() that expects the XMLHttpRequest from the "getSomethingElse" method.
I also tried to use the scope.$digest() but I got the same results...
The Controller:
app.controller('mainCtrl', ['$scope', 'myService', function($scope,
myService) {
$scope.category = null;
myService.getSomethingElse().then(function(res) {
$scope.somethingElse = res.data;
});
$scope.$watch('category', function() {
if ($scope.category !== null) {
myService.getListByCat($scope.category.name).then(function(res) {
$scope.list = res.data;
});
}
else {
myService.getLongList().then(function(res) {
$scope.list = res.data;
});
}
});
}]);
The Service:
app.service('myService', ['$http', function($http) {
this.getListByCat = function(category) {
return $http.get('getting-list?cat=' + category);
};
this.getLongList = function() {
return $http.get('getting-long-list');
};
this.getSomethingElse = function() {
return $http.get('getting-something-else');
};
}]);
The Test
describe('Testing mainCtrl', function() {
var scope, ctrl;
var myServiceMock = {
getSomethingElse: jasmine.createSpy().and.returnValue(1),
getListByCat: jasmine.createSpy().and.returnValue(2)
};
beforeEach(function() {
module('app');
inject(function($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('mainCtrl', {
$scope: scope,
myService: myServiceMock
});
});
});
it('should update the list by selected category', function() {
expect(scope.category).toBeNull();
expect(scope.list).toBeUndefined();
scope.category = {
id: 1,
name: 'Jobs'
};
scope.$apply();
expect(myServiceMock.getSomethingElse).toHaveBeenCalled();
expect(myServiceMock.getListByCat).toHaveBeenCalled();
});
});
The test is passing but only if I use the httpBackend.expectGET() that expects the XMLHttpRequest from the "getSomethingElse" method.
This is because your myServiceMock is not replacing the original myService. You have various ways to test this - one of them is given below. Here we are replacing myService with the service mock:-
beforeEach(function() {
module('app');
module(function($provide){
$provide.factory('myServiceMock',
function(){
return myServiceMock;
);
});
inject(function($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('mainCtrl', {
$scope: scope,
myService: myServiceMock
});
});
});
You can add your watcher like this.
$scope.categoryWatcher = categoryWatcher;
$scope.$watch('category', categoryWatcher);
function categoryWatcher() {
if ($scope.category !== null) {
myService.getListByCat($scope.category.name).then(function(res) {
$scope.list = res.data;
});
}
else {
myService.getLongList().then(function(res) {
$scope.list = res.data;
});
}
}
and in Unit testing just create new it construct for that handler
it('should test categoryWatcher for null value', function(){
$scope.category = null;
$scope.categoryWatcher();
// your expectations
});
it('should test categoryWatcher for "desiredValue" value', function(){
$scope.category = "desiredValue";
$scope.categoryWatcher();
// your expectations
});
that way, if&else clauses will be taken in the test.
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 written a unit test like below
describe('modals', function() {
beforeEach(module('DetailsApp'));
var controller, rootScope, templateCache, compile, http, httpBackend;
var uibModalInstance = {
dismiss: function(message) {
},
close: function(message) {
}
};
var plugins = {
get: function(plugin) {
if(plugin == 'Workorder'){
return {
'workorder_id': 'workorder_id'
};
} else if(plugin == 'CompanyInfo'){
return {
'company_name': 'company_name',
'company_id': 'company_id'
};
}
}
};
beforeEach(module(function($provide) {
$provide.value('$uibModalInstance', uibModalInstance);
$provide.value('plugins', plugins);
}));
beforeEach(inject(function($controller, $templateCache, $compile, $rootScope, $http, $httpBackend) {
controller = $controller;
templateCache = $templateCache;
compile = $compile;
rootScope = $rootScope;
http = $http;
httpBackend = $httpBackend;
}));
describe('When modal functions are called', function() {
it('they should be called correctly', function() {
var $scope = {};
var companyRatingHistory = controller('companyRatingHistory', { $scope: $scope });
spyOn(uibModalInstance, 'dismiss');
spyOn(uibModalInstance, 'close');
$scope.cancel();
expect(uibModalInstance.dismiss).toHaveBeenCalledWith('cancel');
$scope.close('close');
expect(uibModalInstance.close).toHaveBeenCalledWith('close');
});
}); });
and found that my code coverage shows an E in plugins else block like below
else if(plugin == 'CompanyInfo'){
return {
'company_name': 'company_name',
'company_id': 'company_id'
};
}
What i have missed in my test. Advance thanks and get any suggestions from anybody who helps me.
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);
});
I am trying to test a controller method that relies on a service call to get some data. The service method returns a promise, and I'd like to test the behavior of the controller if the promise is resolved or rejected.
I have come up with this approach to vary the behavior of my mocked service method, but it does not work. The getDataSuccess flag is always true when the mocked getData method is called. Here's what I have so far:
Controller:
app.controller('myController', function($scope, myService) {
myService.getData()
.then(function (data) {
$scope.data = data;
},
function (data) {
$scope.serverError = data;
});
});
Test:
describe('myController', function () {
var ctl, serviceMock, getDataSuccess, scope;
beforeEach(function() {
getDataSuccess = true;
serviceMock = {};
module('app', function ($provide) {
$provide.value('myService', serviceMock);
});
inject(function ($q) {
serviceMock.getData = function () {
var defer = $q.defer();
if (getDataSuccess) {
defer.resolve("theData");
} else {
defer.reject("theData");
}
return defer.promise;
};
});
});
beforeEach(inject(function ($rootScope, $controller, $httpBackend, myService) {
scope = $rootScope.$new();
ctl = $controller('myController', {
$scope: scope,
myService: myService,
});
}));
describe('myController loading data', function () {
it('should set $scope.data if data load succeeds', function () {
getDataSuccess = true;
scope.$apply();
expect(scope.data).toEqual("theData");
});
it('should set $scope.serverError if data load fails', function () {
getDataSuccess = false;
scope.$apply();
expect(scope.serverError).toEqual("theData");
});
});
});
Clearly I'm missing something here. The order of execution is not what I was expecting. What's the proper way to do this sort of thing?
Here's this example in Plunker: http://plnkr.co/edit/ODyslivLorjaLM4EqlEF?p=preview
myService.getData function is called where myController is initialized. So if you want to change the behavior getData function by setting getDataSuccess, you need to initialize myController after you set getDataSuccess true/false.
What I recommend is something like this.
In appSpec.js
describe('myController', function () {
var ctl, serviceMock, getDataSuccess, scope;
beforeEach(function() {
getDataSuccess = true;
serviceMock = {};
module('app', function ($provide) {
$provide.value('myService', serviceMock);
});
inject(function ($q) {
serviceMock.getData = function () {
var defer = $q.defer();
if (getDataSuccess) {
defer.resolve("theData");
} else {
defer.reject("theData");
}
return defer.promise;
};
});
});
beforeEach(inject(function ($rootScope, $controller, $httpBackend, myService) {
scope = $rootScope.$new();
//
// ctl = $controller('myController', {
// $scope: scope,
// myService: myService,
// });
}));
describe('myController loading data', function () {
it('should set $scope.data if data load succeeds', inject(function($controller, myService){
getDataSuccess = true;
ctl = $controller('myController', {
$scope: scope,
myService: myService,
});
scope.$apply();
expect(scope.data).toEqual("theData");
}));
it('should set $scope.serverError if data load fails', inject(function($controller, myService){
getDataSuccess = false;
ctl = $controller('myController', {
$scope: scope,
myService: myService,
});
scope.$apply();
expect(scope.serverError).toEqual("theData");
}));
});
});
This is updated plunker.