How to conditionally flush a $timeout in Angular unit test - javascript

I have code that is something like this:
function doThing() {
if (invalidInput) {
console.error('Invalid input.');
return;
}
$timeout(function() {
MyService.doThing();
}, 1000);
}
I want to test that MyService.doThing isn't called when invalid input is passed in.
If I call doThing(invalidInput) without doing $timeout.flush(), MyService.doThing wouldn't be called, regardless of whether I have lines 2-5. So to really test whether MyService.doThing is called when invalid input is passed in, I need to call $timeout.flush.
The problem is that it throws an error if I try to flush when there's nothing to flush. Error: No deferred tasks to be flushed.
How can I handle this scenario? I'd like to do something like, $timeout.flushIfFlushable().

I suggest define two separate unit tests to verify the behavior of your doThing function. Try following code:
Controller
(function () {
'use strict';
angular.module('myApp', [])
.controller('MainCtrl', function ($timeout, MyService) {
var vm = this;
vm.invalidInput = true;
vm.doThing = doThing;
function doThing() {
if (vm.invalidInput) {
return;
}
$timeout(function () {
MyService.doThing();
}, 1000);
}
});
})();
Service
(function () {
'use strict';
angular.module('myApp').service('MyService', MyService);
function MyService() {
this.doThing = function () {
// doThing code
};
}
})();
Unit test
'use strict';
describe('Controller: MainCtrl', function () {
beforeEach(module('myApp'));
var vm,
$timeout,
MyService;
beforeEach(inject(function (_$controller_, _$timeout_, _MyService_) {
$timeout = _$timeout_;
MyService = _MyService_;
vm = _$controller_('MainCtrl', {
$timeout: $timeout,
MyService: MyService
});
}));
it('should call doThing for valid inputs', function () {
spyOn(MyService, 'doThing').andCallThrough();
vm.invalidInput = false;
vm.doThing();
$timeout.flush();
expect(MyService.doThing).toHaveBeenCalled();
});
it('should not call doThing for invalid inputs', function () {
spyOn(MyService, 'doThing').andCallThrough();
vm.invalidInput = true;
vm.doThing();
expect(MyService.doThing).not.toHaveBeenCalled();
});
});
With the first test we expect to call MyService.doThing() function.
On other hand, if you have invalidInput as true, the previous function should not be called.
I hope It helps.

No uncertainty is welcome in unit tests, it should be predictable whether there is something to flush for $timeout or not.
In this piece of code two cases should be tested, in both $timeout shouldn't be a blackbox, it should be a stub instead (optionally a spy that wraps around the real service).
beforeEach(module('app', ($provide) => {
$provide.decorator('$timeout', ($delegate) => {
var timeoutSpy = jasmine.createSpy().and.returnValue($delegate);
// methods aren't copied automatically to spy
return angular.extend(timeoutSpy, $delegate);
});
}));
The first is falsey invalidInput:
...
MyService.doThing();
expect($timeout).not.toHaveBeenCalled();
And the second one is truthy invalidInput:
...
MyService.doThing();
expect($timeout).toHaveBeenCalledWith(jasmine.any(Function), 1000);
$timeout.flush();
expect(MyService.doThing).toHaveBeenCalledTimes(2);
Disregarding this case, it is generally a good thing to return promises from promise-powered functions:
function doThing() {
if (invalidInput) {
console.error('Invalid input.');
return;
}
return $timeout(function() {
return MyService.doThing();
}, 1000);
}
This way the callers (most likely specs) may have some control on function's asynchronous behaviour.
Answering the question directly, the expected way to do 'flushIfFlushable()' is
try {
$timeout.verifyNoPendingTasks(); // just for semantics, not really necessary
$timeout.flush();
} catch (e) {}
Which should be avoided for the reasons listed above.

Related

Angular mocha test not firing success/error

I am currently testing a controller in mocha. The controller has an activate function which should fire success/failure based on the response. I cannot get the failure or success functions to fire during my tests.
viewController.js:
(function() {
'use strict';
angular
.module('app')
.controller('viewCtrl', viewCtrl);
function viewCtrl(Service) {
vm.Service = Service;
activate();
function activate() {
vm.Service.get().then(success, failure);
function success(data) {
if (!data || data == 401) {
failure(data);
}
}
function failure(error) {
if (error) {
console.error("Loading question failed:", error);
vm.Service.set();
}
}
}
}
})();
viewControllerTest.js:
describe('question_view_controller', function() {
var httpBackend, controller;
var expect = chai.expect;
var assert = chai.assert;
var Service = {};
var createController;
beforeEach(function(){
angular.mock.module('ui.router');
angular.mock.module('question');
Service = {
set : sinon.stub(),
get : sinon.stub().returns(Promise.reject({error:{}}));
}
})
beforeEach(inject(function($httpBackend,$controller,$q){
httpBackend = $httpBackend;
createController = function(){
return $controller('ViewCtrl', {
$scope: scope,
Service: Service
});;
}
}));
afterEach(function(){
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
describe('activate', function () {
describe('get.then() error', function(){
beforeEach(function(){
Service.get.returns(Promise.reject({error:{}}))
})
it('should do nothing and setFailedQuestion should be called once', function(){
vm = createController();
scope.$digest();
expect(vm.Service.set.callCount).to.equal('1');
})
})
});
});
If anyone could point out my mistake or provide any insight that would be great. Anymore questions please ask.
UPDATE:
Edited code to reflect danday74's answer. Still not working.
UPDATE:
Edited code to reflect danday74's comment. Still not working.
you will need to call scope digest. you will need to inject $rootScope and then ...
vm = createController();
$rootScope.$digest();
expect(vm.Service.set.callCount).to.equal('1');
$digest() causes the THEN blocks to be executed.
similar approach to $httpBackend.flush() if you have ever used that.

How to test function wrapped in a promis

My ctrl is like this:
(function() {
'use strict';
angular
.module('App')
.controller('DeviceStatesCtrl', DeviceStatesCtrl);
function DeviceStatesCtrl( $rootScope, $scope, $translate,DeviceStatesService) {
var vm = this;
DeviceStatesService.getObject().then(function(response){
vm.init(response);
});
vm.init= function(response){
$translate(['table.title']).then(function(translate){
some stuff here
}));
}
}})();
My jasmine test is like this:
describe('app module', function() {
//var controller = null;
var $controller, $translate,$compile,createController,DeviceStatesService,$translate, scope;
var mockInit= sth;
beforeEach(function () {
module('App');
});
// Provide will help us create fake implementations for our dependencies, do not useful
module(function($provide) {
// Fake StoreService Implementation returning a promise
//nothing works :(
$provide.value('DeviceStatesService', {
getStatesObject: function() {
return {
then: function(callback) {
return callback([{ some: "thing", hoursInfo: {isOpen: true}}]);
}
};
}
});
});
return null;
});
beforeEach(inject(function($controller,$rootScope, _$translate_, _DeviceStatesService_) {
scope = $rootScope.$new();
//for 'controller as' syntax
$controller('DeviceStatesCtrl as deviceStat', {
$scope: scope
});
createController = function(params) {
return $controller("DeviceStatesCtrl as deviceStat", {
$scope: scope,
$stateParams: params || {}
});
};
}));
describe("Unit:Device States controller", function() {
//test init function
it("init function get called correctly", function() {
//spyOn(DeviceStatesService, 'getStatesObject').and.callThrough();
//createController();
//expect(DeviceStatesService.getStatesObject).toHaveBeenCalled();
expect(scope.deviceStat.init).toBeDefined();
//in init, all things are warpped in the $translate
//spyOn(scope.deviceStat, 'init');
scope.deviceStat.init(mockInit);
scope.deviceStat.setChart('All');
//expect(scope.deviceStat.totalNum).toEqual(22);
});
});
});
My question is how to test the init function and the stuff in it? The init function is in a promise, which I do not know how to call it. As my code scope.deviceStat.init(mockInit), it do not work. Another question is in the $translate promise, how to pass parameter in it?
You use the done function, which is a parameter passed to the spec via the jasmine function it.. Example..
describe("My Test set", function() {
it("My Test", function(done) {
doAsync().then(function(result) {
done();
}).catch(function(err) {
fail();
}
}
}
Hope this helps.

AngularJS : testing a factory that returns a promise, while mocking a service that uses $http

I've got a service that has the following method (among others), which returns an $http promise
function sessionService($http, serviceRoot) {
return {
getAvailableDates: function () {
return $http.get(serviceRoot + '/session/available_dates');
}
};
};
angular.module('app').service('sessionService', ['$http', 'serviceRoot', sessionService]);
And then another factory that kinda wraps it and caches/adds data to localStorage. This returns a regular promise
angular.module('app')
.factory('AvailableDates', AvailableDates);
AvailableDates.$inject = ['sessionService', '$window', '$q'];
function AvailableDates(sessionService, $window, $q) {
var availableDates = [];
return {
getAvailableDates: getAvailableDates
};
function getAvailableDates() {
var deferred = $q.defer();
var fromStorage = JSON.parse($window.sessionStorage.getItem('validDates'));
if (availableDates.length > 0) {
deferred.resolve(availableDates);
} else if (fromStorage !== null) {
deferred.resolve(fromStorage);
} else {
sessionService.getAvailableDates()
.success(function (result) {
availableDates = result;
$window.sessionStorage.setItem('validDates', JSON.stringify(availableDates));
deferred.resolve(availableDates);
});
}
return deferred.promise;
}
}
This all works fine. My problem is I can't figure out how to test this thing while mocking the sessionService. I've read all the related stackoverflow questions, and tried all kinds of different things, to no avail.
Here's what my test currently looks like:
describe('testing AvailableDates factory', function () {
var mock, service, rootScope, spy, window, sessionStorageSpy, $q;
var dates = [ "2014-09-27", "2014-09-20", "2014-09-13", "2014-09-06", "2014-08-30" ];
var result;
beforeEach(module('app'));
beforeEach(function() {
return angular.mock.inject(function (_sessionService_, _AvailableDates_, _$rootScope_, _$window_, _$q_) {
mock = _sessionService_;
service = _AvailableDates_;
rootScope = _$rootScope_;
window = _$window_;
$q = _$q_;
});
});
beforeEach(inject(function () {
// my service under test calls this service method
spy = spyOn(mock, 'getAvailableDates').and.callFake(function () {
return {
success: function () {
return [ "2014-09-27", "2014-09-20", "2014-09-13", "2014-09-06", "2014-08-30" ];
},
error: function() {
return "error";
}
};
});
spyOn(window.sessionStorage, "getItem").and.callThrough();
}));
beforeEach(function() {
service.getAvailableDates().then(function(data) {
result = data;
// use done() here??
});
});
it('first call to fetch available dates hits sessionService and returns dates from the service', function () {
rootScope.$apply(); // ??
console.log(result); // this is printing undefined
expect(spy).toHaveBeenCalled(); // this passes
expect(window.sessionStorage.getItem).toHaveBeenCalled(); // this passes
});
});
I've tried various things but can't figure out how to test the result of the AvailableDates.getAvailableDates() call. When I use done(), I get the error:
Timeout - Async callback was not invoked withing timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL (I've tried overriding this value, no luck).
If I take out the done(), and just call rootScope.$apply() after the .then is called, I get an undefined value as my result.
What am I doing wrong?
I see more issues in your example.
The main problem is the success definition in the mock. Success is a function, which has a function as a parameter - callback. Callback is called when data is received - data is passed as the first argument.
return {
success: function (callback) {
callback(dates);
}
};
Simplified working example is here http://plnkr.co/edit/Tj2TZDWPkzjYhsuSM0u3?p=preview
In this example, mock is passed to the provider with the module function (from ngMock) - you can pass the object with a key (service name) and value (implementation). That implementation will be used for injection.
module({
sessionService:sessionServiceMock
});
I think test logic should be in one function (test), split it into beforeEach and test is not a good solution. Test is my example; it's more readable and has clearly separated parts - arrange, act, assert.
inject(function (AvailableDates) {
AvailableDates.getAvailableDates().then(function(data) {
expect(data).toEqual(dates);
done();
});
rootScope.$apply(); // promises are resolved/dispatched only on next $digest cycle
expect(sessionServiceMock.getAvailableDates).toHaveBeenCalled();
expect(window.sessionStorage.getItem).toHaveBeenCalled();
});

Mocking asynchronous service function using angular/karma/jasmine

The test in this code does not succeed. I can't seem to successfully test the return of an asynchronous function.
describe('mocking services', function () {
var someService, deferred;
beforeEach(function () {
module(function($provide){
$provide.factory('someService', function($q){
return{
trySynch: function(){
return 33;
},
tryAsynch: function(){
deferred = $q.defer();
return deferred.promise;
}
};
});
});
inject(function (_someService_) {
someService = _someService_;
});
});
it('should be able to test values from both functions', function () {
expect(someService.trySynch()).toEqual(33);
var retVal;
someService.tryAsynch().then(function(r){
retVal = r;
});
deferred.resolve(44);
expect(retVal).toEqual(44);
});
});
When I run it I get the following error:
Chrome 36.0.1985 (Mac OS X 10.9.4) mocking services should be able to test values from both functions FAILED
Expected undefined to equal 44.
Error: Expected undefined to equal 44.
at null.<anonymous> (/Users/selah/WebstormProjects/macrosim-angular/test/spec/services/usersAndRoles-service-test.js:34:24)
How can I make this test pass?
When mocking async calls with $q, you need to use $rootScope.$apply() because of how $q is implemented.
Specifically, the .then method does not get called synchronously, it is designed to always be async, regardless of how it was called - sync or async.
To achieve that, $q is integrated with $rootScope. Therefore, in your unit tests, you need to notify the $rootScope that something was changed (ie - trigger a digest cycle). To do that, you call $rootScope.$apply()
See here (specifically the "Differences between Kris Kowal's Q and $q section")
Working code looks like this:
describe('mocking services', function () {
var someService, deferred, rootScope;
beforeEach(function () {
module(function($provide){
$provide.factory('someService', function($q){
return{
trySynch: function(){
return 33;
},
tryAsynch: function(){
deferred = $q.defer();
return deferred.promise;
}
};
});
});
inject(function ($injector) {
someService = $injector.get('someService');
rootScope = $injector.get('$rootScope');
});
});
it('should be able to test values from both functions', function () {
expect(someService.trySynch()).toEqual(33);
var retVal;
someService.tryAsynch().then(function(r){
retVal = r;
});
deferred.resolve(44);
rootScope.$apply();
expect(retVal).toEqual(44);
});
});
$q's deferred is still resolving asynchronously.
Quick test, albeit in an older version of Angular: http://plnkr.co/edit/yg2COXG0TWBYniXOwJYb
This test should work:
it('should be able to test values from both functions', function (done) {
expect(someService.trySynch()).toEqual(33);
someService.tryAsynch().then(function(r){
expect(r).toEqual(44);
done();
});
deferred.resolve(44);
});
If I run rootScope.$apply() before my expect clause that tests my asynchronous function then the test succeeds. Also, it fails if I supply an incorrect value, as I would expect it to.
So my test is functional, but I don't however understand why rootScope.$apply() is important here, so if anyone wants to copy my code and provide an explanation I will gladly mark your answer as the correct answer!
My working test code looks like this:
describe('mocking services', function () {
var someService, deferred, rootScope;
beforeEach(function () {
module(function($provide){
$provide.factory('someService', function($q){
return{
trySynch: function(){
return 33;
},
tryAsynch: function(){
deferred = $q.defer();
return deferred.promise;
}
};
});
});
inject(function ($injector) {
someService = $injector.get('someService');
rootScope = $injector.get('$rootScope');
});
});
it('should be able to test values from both functions', function () {
expect(someService.trySynch()).toEqual(33);
var retVal;
someService.tryAsynch().then(function(r){
retVal = r;
});
deferred.resolve(44);
rootScope.$apply();
expect(retVal).toEqual(44);
});
});

Test a controller with success() and error ()

I'm trying to work out the best way to unit test success and error callbacks in controllers. I am able to mock out service methods, as long as the controller only uses the default $q functions such as 'then' (see the example below). I'm having an issue when the controller responds to a 'success' or 'error' promise. (Sorry if my terminology is not correct).
Here is an example controller \ service
var myControllers = angular.module('myControllers');
myControllers.controller('SimpleController', ['$scope', 'myService',
function ($scope, myService) {
var id = 1;
$scope.loadData = function () {
myService.get(id).then(function (response) {
$scope.data = response.data;
});
};
$scope.loadData2 = function () {
myService.get(id).success(function (response) {
$scope.data = response.data;
}).error(function(response) {
$scope.error = 'ERROR';
});
};
}]);
cocoApp.service('myService', [
'$http', function($http) {
function get(id) {
return $http.get('/api/' + id);
}
}
]);
I have the following test
'use strict';
describe('SimpleControllerTests', function () {
var scope;
var controller;
var getResponse = { data: 'this is a mocked response' };
beforeEach(angular.mock.module('myApp'));
beforeEach(angular.mock.inject(function($q, $controller, $rootScope, $routeParams){
scope = $rootScope;
var myServiceMock = {
get: function() {}
};
// setup a promise for the get
var getDeferred = $q.defer();
getDeferred.resolve(getResponse);
spyOn(myServiceMock, 'get').andReturn(getDeferred.promise);
controller = $controller('SimpleController', { $scope: scope, myService: myServiceMock });
}));
it('this tests works', function() {
scope.loadData();
expect(scope.data).toEqual(getResponse.data);
});
it('this doesnt work', function () {
scope.loadData2();
expect(scope.data).toEqual(getResponse.data);
});
});
The first test passes and the second fails with the error "TypeError: Object doesn't support property or method 'success'". I get that in this instance that getDeferred.promise
does not have a success function. Okay here is the question, what is a nice way to write this test so that I can test the 'success', 'error' & 'then' conditions of a mocked service ?
I'm starting to think that I should avoid the use of success() and error() in my controllers...
EDIT
So after thinking about this some more, and thanks to the detailed answer below, I've come to the conclusion that the handling the success and error callbacks in the controller is bad. As HackedByChinese mentions below success\error is syntactic sugar that is added by $http. So, in actual fact, by trying to handle success \ error I am letting $http concerns leak into my controller, which is exactly what I was trying to avoid by wrapping the $http calls in a service. The approach I'm going to take is to change the controller not to use success \ error:
myControllers.controller('SimpleController', ['$scope', 'myService',
function ($scope, myService) {
var id = 1;
$scope.loadData = function () {
myService.get(id).then(function (response) {
$scope.data = response.data;
}, function (response) {
$scope.error = 'ERROR';
});
};
}]);
This way I can test the error \ success conditions by calling resolve() and reject() on the deferred object:
'use strict';
describe('SimpleControllerTests', function () {
var scope;
var controller;
var getResponse = { data: 'this is a mocked response' };
var getDeferred;
var myServiceMock;
//mock Application to allow us to inject our own dependencies
beforeEach(angular.mock.module('myApp'));
//mock the controller for the same reason and include $rootScope and $controller
beforeEach(angular.mock.inject(function($q, $controller, $rootScope, $routeParams) {
scope = $rootScope;
myServiceMock = {
get: function() {}
};
// setup a promise for the get
getDeferred = $q.defer();
spyOn(myServiceMock, 'get').andReturn(getDeferred.promise);
controller = $controller('SimpleController', { $scope: scope, myService: myServiceMock });
}));
it('should set some data on the scope when successful', function () {
getDeferred.resolve(getResponse);
scope.loadData();
scope.$apply();
expect(myServiceMock.get).toHaveBeenCalled();
expect(scope.data).toEqual(getResponse.data);
});
it('should do something else when unsuccessful', function () {
getDeferred.reject(getResponse);
scope.loadData();
scope.$apply();
expect(myServiceMock.get).toHaveBeenCalled();
expect(scope.error).toEqual('ERROR');
});
});
As someone had mentioned in a deleted answer, success and error are syntactic sugar added by $http so they aren't there when you create your own promise. You have two options:
1 - Don't mock the service and use $httpBackend to setup expectations and flush
The idea is to let your myService act like it normally would without knowing it's being tested. $httpBackend will let you set up expectations and responses, and flush them so you can complete your tests synchronously. $http won't be any wiser and the promise it returns will look and function like a real one. This option is good if you have simple tests with few HTTP expectations.
'use strict';
describe('SimpleControllerTests', function () {
var scope;
var expectedResponse = { name: 'this is a mocked response' };
var $httpBackend, $controller;
beforeEach(module('myApp'));
beforeEach(inject(function(_$rootScope_, _$controller_, _$httpBackend_){
// the underscores are a convention ng understands, just helps us differentiate parameters from variables
$controller = _$controller_;
$httpBackend = _$httpBackend_;
scope = _$rootScope_;
}));
// makes sure all expected requests are made by the time the test ends
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('should load data successfully', function() {
beforeEach(function() {
$httpBackend.expectGET('/api/1').response(expectedResponse);
$controller('SimpleController', { $scope: scope });
// causes the http requests which will be issued by myService to be completed synchronously, and thus will process the fake response we defined above with the expectGET
$httpBackend.flush();
});
it('using loadData()', function() {
scope.loadData();
expect(scope.data).toEqual(expectedResponse);
});
it('using loadData2()', function () {
scope.loadData2();
expect(scope.data).toEqual(expectedResponse);
});
});
describe('should fail to load data', function() {
beforeEach(function() {
$httpBackend.expectGET('/api/1').response(500); // return 500 - Server Error
$controller('SimpleController', { $scope: scope });
$httpBackend.flush();
});
it('using loadData()', function() {
scope.loadData();
expect(scope.error).toEqual('ERROR');
});
it('using loadData2()', function () {
scope.loadData2();
expect(scope.error).toEqual('ERROR');
});
});
});
2 - Return a fully-mocked promise
If the thing you're testing has complicated dependencies and all the set-up is a headache, you may still want to mock the services and the calls themselves as you have attempted. The difference is that you'll want to fully mock promise. The downside of this can be creating all the possible mock promises, however you could make that easier by creating your own function for creating these objects.
The reason this works is because we pretend that it resolves by invoking the handlers provided by success, error, or then immediately, causing it to complete synchronously.
'use strict';
describe('SimpleControllerTests', function () {
var scope;
var expectedResponse = { name: 'this is a mocked response' };
var $controller, _mockMyService, _mockPromise = null;
beforeEach(module('myApp'));
beforeEach(inject(function(_$rootScope_, _$controller_){
$controller = _$controller_;
scope = _$rootScope_;
_mockMyService = {
get: function() {
return _mockPromise;
}
};
}));
describe('should load data successfully', function() {
beforeEach(function() {
_mockPromise = {
then: function(successFn) {
successFn(expectedResponse);
},
success: function(fn) {
fn(expectedResponse);
}
};
$controller('SimpleController', { $scope: scope, myService: _mockMyService });
});
it('using loadData()', function() {
scope.loadData();
expect(scope.data).toEqual(expectedResponse);
});
it('using loadData2()', function () {
scope.loadData2();
expect(scope.data).toEqual(expectedResponse);
});
});
describe('should fail to load data', function() {
beforeEach(function() {
_mockPromise = {
then: function(successFn, errorFn) {
errorFn();
},
error: function(fn) {
fn();
}
};
$controller('SimpleController', { $scope: scope, myService: _mockMyService });
});
it('using loadData()', function() {
scope.loadData();
expect(scope.error).toEqual("ERROR");
});
it('using loadData2()', function () {
scope.loadData2();
expect(scope.error).toEqual("ERROR");
});
});
});
I rarely go for option 2, even in big applications.
For what it's worth, your loadData and loadData2 http handlers have an error. They reference response.data but the handlers will be called with the parsed response data directly, not the response object (so it should be data instead of response.data).
Don't mix concerns!
Using $httpBackend inside a controller is a bad Idea since you are mixing concerns inside your Test. Whether you retrieve data from an Endpoint or not is not a concern of the Controller, is a concern of the DataService you are calling.
You can see this more clearly if you change the Endpoint Url inside the service you will then have to modify both tests: the service Test and the Controller Test.
Also as previously mentioned, the use of success and error are syntactic sugar and we should stick to the use of then and catch. But in reality you may find yourself in the need of testing "legacy" code. So for that I'm using this function:
function generatePromiseMock(resolve, reject) {
var promise;
if(resolve) {
promise = q.when({data: resolve});
} else if (reject){
promise = q.reject({data: reject});
} else {
throw new Error('You need to provide an argument');
}
promise.success = function(fn){
return q.when(fn(resolve));
};
promise.error = function(fn) {
return q.when(fn(reject));
};
return promise;
}
By calling this function you will get a true promise that respond to then and catch methods when you need to and will also work for the success or error callbacks. Note that the success and error returns a promise itself so it will work with chained then methods.
(NOTE: On the 4th and 6th line the function returns resolve and reject values inside the data property of an object. This is to mock the Behavior of $http since it returns the data, http Status etc.)
Yes, do not use $httpbackend in your controller, because we don't need to make real requests, you just need to make sure that one unit is doing it's job exactly as expected, have a look on this simple controller tests, it's easy to understand
/**
* #description Tests for adminEmployeeCtrl controller
*/
(function () {
"use strict";
describe('Controller: adminEmployeeCtrl ', function () {
/* jshint -W109 */
var $q, $scope, $controller;
var empService;
var errorResponse = 'Not found';
var employeesResponse = [
{id:1,name:'mohammed' },
{id:2,name:'ramadan' }
];
beforeEach(module(
'loadRequiredModules'
));
beforeEach(inject(function (_$q_,
_$controller_,
_$rootScope_,
_empService_) {
$q = _$q_;
$controller = _$controller_;
$scope = _$rootScope_.$new();
empService = _empService_;
}));
function successSpies(){
spyOn(empService, 'findEmployee').and.callFake(function () {
var deferred = $q.defer();
deferred.resolve(employeesResponse);
return deferred.promise;
// shortcut can be one line
// return $q.resolve(employeesResponse);
});
}
function rejectedSpies(){
spyOn(empService, 'findEmployee').and.callFake(function () {
var deferred = $q.defer();
deferred.reject(errorResponse);
return deferred.promise;
// shortcut can be one line
// return $q.reject(errorResponse);
});
}
function initController(){
$controller('adminEmployeeCtrl', {
$scope: $scope,
empService: empService
});
}
describe('Success controller initialization', function(){
beforeEach(function(){
successSpies();
initController();
});
it('should findData by calling findEmployee',function(){
$scope.findData();
// calling $apply to resolve deferred promises we made in the spies
$scope.$apply();
expect($scope.loadingEmployee).toEqual(false);
expect($scope.allEmployees).toEqual(employeesResponse);
});
});
describe('handle controller initialization errors', function(){
beforeEach(function(){
rejectedSpies();
initController();
});
it('should handle error when calling findEmployee', function(){
$scope.findData();
$scope.$apply();
// your error expectations
});
});
});
}());

Categories

Resources