Mocking $q.all in jasmine for angularjs controller - javascript

I am writing jasmine test cases for an angular controller which have an init function having an array of promises to be resolved:
(function () {
angular.controller("xyz", ['$scope', "Utility", "Api",
function ($scope, Utility, Api) {
var locals = $scope.locals = {
id: 1,
amount: 2,
products: 3
};
function init() {
locals.busyPromise = Utility.resolveAll(
{
name: 'a',
promise: Api.get,
then: function (response) { locals.id = 2; }
},
{
name: 'b',
promise: Api.find,
then: function (response) { locals.amount = 4; }
}
).then(function (response) { locals.products = 6; });
}
init();
}
])
})();
Utility is an external script which resolves each promise in the array and executes their then function to set local properties. Once all the promises in the array are resolved, it moves to then function of resolveAll and execute it.
My question is, how can we mock Utility.resolveAll while injecting dependencies in Jasmine. In my case, whatever I tried, it never enters the then block of individual promises and goes directly to then block of resolveAll.

Here what I would do:
First if all, mock Utility and Api services
let Utility, Api;
beforeEach(() => {
Utility = jasmine.createSpyObj('Utility', ['resolveAll']);
Api = jasmine.createSpyObj('Api', ['find', 'get']);
});
Then in tests:
it('should test component startup', function() {
let resolveObjects;
Utility.resolveAll.and.callFake(function(...args) {
resolveObjects = args; // save all arguments, passed to `resolveAll` method
return $q.when();
})
$scope = $rootScope.$new();
let controller = $controller('xyz', {$scope, Utility, Api});
$rootScope.digest(); // resolve $q.when so you can test overall promise
expect($scope.locals.products).toBe(6); // test overall promise
// now, test all the arguments
// 0
expect(resolveObjects[0].name).toBe('a');
resolveObjects[0].promise();
expect(Api.get).toHaveBeenCalledTimes(1);
resolveObjects[0].then();
expect($scope.locals.id).toBe(4);
// 1
expect(resolveObjects[1.name).toBe('b');
resolveObjects[1].promise();
expect(Api.find).toHaveBeenCalledTimes(1);
resolveObjects[1].then();
expect($scope.locals.products).toBe(4);
});

Related

cant update jasmine spy

Hi I have a Angular service that uses another service that loads data from the local storage on init.
angular
.module('app')
.factory('localStorage', function ($window)
{
if (!$window.localStorage)
{
// throw Error
}
return $window.localStorage;
});
angular
.module('app')
.factory('session', function (localStorage)
{
var container = JSON.parse(localStorage.getItem('sessionContainer'));
return {
getUser: getUser
};
});
Now i want to test the session service.
describe('SessionService', function ()
{
var service;
var localStorageMock;
// Load the module.
beforeEach(module('appRegistration'));
// Create mocks.
beforeEach(function ()
{
logMock = {};
localStorageMock = jasmine.createSpyObj('localStorageServiceMockSpy', ['setItem', 'getItem']);
localStorageMock.getItem.and.returnValue('{}');
module(function ($provide)
{
$provide.value('localStorage', localStorageMock);
});
inject(function (_session_)
{
service = _session_;
});
});
it('should call `getItem` on the `localStorageService` service', function ()
{
expect(localStorageMock.getItem).toHaveBeenCalledWith('sessionContainer');
});
describe('getUser method', function ()
{
it('should return an empty object when the user is not set', function ()
{
var result = service.getUser();
expect(result).toEqual({});
});
it('should return the user data', function ()
{
// localStorageMock.getItem.and.returnValue('{"user":{"some":"data"}}');
var result = service.getUser();
expect(result).toEqual({some: 'user data'});
});
});
});
As you can see in the should return the user data section.
I need a way to update the container so getUser returns the expected data.
I tried to update the getItem spy, but this does not work. The localStorageMock is already injected in the session service when i want to change the spy.
Any help?
The most simple way is to have a variable with mocked value that is common for both function scopes:
var getItemValue;
beforeEach({
localStorage: {
getItem: jasmine.createSpy().and.callFake(function () {
return getItemValue;
}),
setItem: jasmine.createSpy()
}
});
...
it('should return the user data', function ()
{
getItemValue = '{"user":{"some":"data"}}';
inject(function (_session_) {
service = _session_;
});
var result = service.getUser();
expect(result).toEqual({some: 'user data'});
});
Notice that inject should be moved from beforeEach to it for all specs (the specs that don't involve getItemValue may use shorter syntax, it('...', inject(function (session) { ... }))).
This reveals the flaw in service design that makes it test-unfriendly.
The solution is to make container lazily evaluated, so there is time to mock it after the app was bootstrapped with inject:
.factory('session', function (localStorage)
{
var containerCache;
function getUser() {
...
return this.container;
}
return {
get container() {
return (containerCache === undefined)
? (containerCache = JSON.parse(localStorage.getItem('sessionContainer')))
: containerCache;
},
getUser: getUser
};
});
Additionally, this makes possible to test session.container as well. In this case localStorageMock.getItem spy value may be redefined whenever needed.

Jasmine test a promise.then function

I try to test my app with Jasmine and got the following problem:
I will calculate something in the then function of my promise. That's the point where I need to test my code.
Here is the code of my controller:
TestCtrl.$inject = ["$scope", "TestService"];
/* ngInject */
function TestCtrl($scope, TestService) {
$scope.loadData = function () {
TestService.getData().then(function (response) {
$scope.data = response.data;
$scope.filtered = $scope.data.filter(function(item){
if(item.id > 1000){
return true;
}
return false;
})
});
}
}
And my Jasmine test code:
describe('TestService tests', function () {
var $q;
beforeEach(function () {
module('pilot.fw.user');
});
beforeEach(inject(function (_$q_) {
$q = _$q_;
}));
describe('UserController Tests', function () {
beforeEach(inject(function (_$httpBackend_, $rootScope, $controller) {
this.scope = $rootScope.$new();
this.$rootscope = $rootScope;
this.$httpBackend = _$httpBackend_;
this.scope = $rootScope.$new();
var TestServiceMock = {
getData: function () {
var deferred = $q.defer();
var result = [{
"id": 1720,
"user": 1132
},
{
"id": 720,
"user": 132
}, {
"id": 1721,
"user": 1132
}];
deferred.promise.data = result;
deferred.resolve(result);
return deferred.promise;
}
};
this.controller = $controller('TestCtrl', {
'$scope': this.scope,
'TestService': TestServiceMock
});
}));
it('test', function(){
this.scope.loadData();
expect(true).toBeTruthy();
})
});
});
The strange thing I don't understand is (tested with console logs):
My promise is created and returned
My loadData function is called and it will call the getData() function from the TestService
Everything inside the then function won't be executed although I return the promise as resolved
So how could I test the code inside the then function?
Thanks for help
the jasmine 'it' method takes a done parameter that you can call for async testing
it('Should be async', function(done) {
someAsyncFunction().then(function(result) {
expect(result).toBe(true);
done();
});
});
Feel free to go as deep as you want, just be sure to call done when EVERYTHING is finished. Jasmine's default timeout is 5 seconds per test, so if the async stuff isn't done by then jasmine will crash. You can change this setting in the configs or set it in the terminal.
This is straight from the jasmine docs, showing you how to handle the default timeout interval
describe("long asynchronous specs", function() {
var originalTimeout;
beforeEach(function() {
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
});
it("takes a long time", function(done) {
setTimeout(function() {
done();
}, 9000);
});
afterEach(function() {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
});
});
I think that if it doesn't work in 10 seconds, you may have faulty methods. ESPECIALLY if you are talking to a local server / db. This stuff should only take this long if you are performing HEAVY computations, or are hitting an external api with a not-so-great internet connection. If everything is local (or stubbed / mocked!) then anything over 5-10 seconds is a definite red flag.
You better watch this https://codecraft.tv/courses/angular/unit-testing/asynchronous/
You have actually 3 ways:
1) use regular it:
it('test', (done) => {
const spy = spyOn(func, 'bar').and.returnValue(Promise.resolve(true));
spy.calls.mostRecent().returnValue.then(res => {
...your expect here...
done();
})
} );
2) use async in beforeEach and it:
it('test', async(() => {
spyOn(func, 'bar').and.returnValue(Promise.resolve(true));
fixture.whenStable().then(res => {
...your expect here...
})
} ));
3) use fakeAsync if you don't have Http or XHR calls:
it('test', fakeAsync(() => {
spyOn(func, 'bar').and.returnValue(Promise.resolve(true));
tick();
...your expect here...
} ));
hope this solution helps. One approach I've found useful when testing is mocking dependencies. I've tried to comment out what I've done as much as possible.
var returnMock, $scope, TestServiceMock, controller;
beforeEach(module('app'));
beforeEach(inject(function($controller) {
returnMock = {
then: jasmine.createSpy(),
};
$scope = {};
// first assumption is You are testing TestService extensively,
// I don't care about what getData has to do to get results
// All I care about is it gets called when I call loadData
TestServiceMock = {
getData: jasmine.createSpy().and.returnValue(returnMock);
};
controller = $controller;
}));
it('should load data when loadData function is called and result set is
under 1000', function() {
controller('TestCtrl', {
$scope,
TestServiceMock
});
// another assumption is your data comes back in such a format
// perhaps in the actual code check whether data exists and proceed
// or do some other action
var returnedData = {
data: [
{
id: 1,
name: 'item 1',
},
]
}
// when I execute the function/method
$scope.loadData();
// I expect getData to be called
expect(TestServiceMock.getData).toHaveBeenCalled();
// I expect then to be called and the reason is I mocked it
expect(returnMock.then).toHaveBeenCalledWith(jasmine.any(Function));
returnMock.then.calls.mostRecent().args[0](returnedData);
// expect data on scope to be equal to my mocked data
expect($scope.data).toEqual(returnedData.data);
// don't expect any result because 1 < 1000
expect($scope.filtered).toEqual([]);
expect($scope.filtered.length).toEqual(0);
});
it('should load data when loadData function is called and result set is over 1000',
function() {
controller('TestCtrl', {
$scope,
TestServiceMock
});
var returnedData = {
data: [
{
id: 1,
name: 'item 1',
},
{
id: 1000,
name: 'item 1000',
},
{
id: 1001,
name: 'item 1000',
},
{
id: 1002,
name: 'item 1002',
}
]
}
$scope.loadData();
expect(TestServiceMock.getData).toHaveBeenCalled();
expect(returnMock.then).toHaveBeenCalledWith(jasmine.any(Function));
returnMock.then.calls.mostRecent().args[0](returnedData);
expect($scope.data).toEqual(returnedData.data);
// expect a result because some entries in the mocked data have id > 1000
expect($scope.filtered).toEqual([
{
id: 1001,
name: 'item 1000',
},
{
id: 1002,
name: 'item 1002',
}]);
expect($scope.filtered.length).toEqual(2);
});
Official Jasmine Docs explain most of the concepts extensively. Hope the solution helps!!!!
Let me tell ya what I do, for Angular 1.x and 2.x+ projects. Use the angular testing tools to get rid of callbacks/nests in your async tests. In angular 1.x, that means using a combination of $q and $rootScope.$apply(). In angular 2.x+, that means using something like fakeAsync.
From the Angular 1.x docs
it('should simulate promise', inject(function($q, $rootScope) {
var deferred = $q.defer();
var promise = deferred.promise;
var resolvedValue;
promise.then(function(value) { resolvedValue = value; });
expect(resolvedValue).toBeUndefined();
// Simulate resolving of promise
deferred.resolve(123);
// Note that the 'then' function does not get called synchronously.
// This is because we want the promise API to always be async, whether or not
// it got called synchronously or asynchronously.
expect(resolvedValue).toBeUndefined();
// Propagate promise resolution to 'then' functions using $apply().
$rootScope.$apply();
expect(resolvedValue).toEqual(123);
}));
The disadvantage is that your code is tied to angular, the advantages are that your code is flat and it's portable to 2.x+!
I was a fan of the mocha test runner that allowed me to return promises in my tests, you could try to get that going, but there are downsides to that as well like needing to modify your code specifically for a test.
In regards to your controller, you should 'return' values like so.
TestCtrl.$inject = ["$scope", "TestService"];
/* ngInject */
function TestCtrl($scope, TestService) {
$scope.loadData = function () {
// Return this call, since it will return a new promise
// This is what let's you do $scope.loadData.then()
return TestService.getData().then(function (response) {
// What you return in here will be the first argument
// of your then method, in the tests / any env
// Ex. return 'foo'
// will result in .then(result => result === 'foo') //=> true
// return one of these, i suggest the data, go SRP!
return $scope.data = response.data;
// I would do this stuff in a separate function, but you
// can return 'filtered' instead if you like.
//
// $scope.filtered = $scope.data.filter(function(item){
// if(item.id > 1000){
// return true;
// }
// return false;
// });
});
}
}
Remember that calling something AFTER 'then' doesn't mean anything, values must be called INSIDE 'then'. Not after it, or before it. But inside it. Like Tom Green and that poor moose in Freddy Got Fingered.

AngularJS $scope variable change in .then() unit testing

I'm trying to unit test a function within my controller but am unable to get a $scope variable to be testable. I'm setting the variable in my controller's .then() and want to unit test to make sure this is set appropriately when it hits the .then block.
My test controller code:
function submit() {
myService.submit().then(function(responseData){
if(!responseData.errors) {
$scope.complete = true;
$scope.details = [
{
value: $scope.formattedCurrentDate
},
{
value: "$" + $scope.premium.toFixed(2)
},
];
} else {
$scope.submitError = true;
}
});
}
Where this service call goes is irrelevant. It will return JSON with action: 'submitted', 'response' : 'some response'. The .then() checks if errors are present on responseData, and if not it should set some details. These $scope.details are what I'm trying to test in my unit test below:
it('should handle submit details', function () {
var result;
var premium = 123.45;
var formattedCurrentDate = "2016-01-04";
var promise = myService.submit();
mockResponse = {
action: 'submitted',
response: 'some response'
};
var mockDetails = [
{
value: formattedCurrentDate
},
{
value: "$"+ premium.toFixed(2)
}
];
//Resolve the promise and store results
promise.then(function(res) {
result = res;
});
//Apply scope changes
$scope.$apply();
expect(mockDetails).toEqual(submitController.details);
});
I'm receiving an error that $scope.details is undefined. I'm not sure how to make the test recognize this $scope data changing within the controller.
Before each and other functions in my unit test:
function mockPromise() {
return {
then: function(callback) {
if (callback) {
callback(mockResponse);
}
}
}
}
beforeEach(function() {
mockResponse = {};
module('myApp');
module(function($provide) {
$provide.service('myService', function() {
this.submit = jasmine.createSpy('submit').and.callFake(mockPromise);
});
});
inject(function($injector) {
$q = $injector.get('$q');
$controller = $injector.get('$controller');
$scope = $injector.get('$rootScope');
myService = $injector.get('myService');
submitController = $controller('myController', { $scope: $scope, $q : $q, myService: myService});
});
});
How do I resolve the promise within my unit test so that I can $scope.$digest() and see the $scope variable change?
You should look how to test promises with jasmine
http://ng-learn.org/2014/08/Testing_Promises_with_Jasmine_Provide_Spy/
using a callFake would do what you try to mock
spyOn(myService, 'submit').and.callFake(function() {
return {
then: function(callback) { return callback(yourMock); }
};
});

AngularJS object losing value inside controller method

I'm a junior dev, so I might be missing something obvious, but I'm feeling a bit loony. I have a simple Angular webapp. I'm attempting to load a hash-dictionary of environment names that correspond to arrays of hosts. {development: ["dev.8090", "host.dev.9009"]} and then use that dictionary to find which host I'm currently on. I should be able to pass the location.host variable to the getEnv method and find the correlating key that will tell me which environment I'm in.
The dictionary loads, but when I try to access it inside of the getEnv method, it reverts to an empty object. Not undefined, mind you, but empty. Here's my code:
var app = angular.module('app', ['ngResource', 'ui.bootstrap', 'ui.router']);
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
}]);
function AppController($scope, $http) {
window.MY_SCOPE = $scope;
$scope.env = "Local";
$scope.dict = {};
$scope.loadDict = function() {
$http.get('api/call/').
success(function(data){
for (env in data.environment) {
// data.environment = array of objects
// [
// {hosts: ["host1", "host2"], name: "string1"},
// {hosts: ["host1", "host2"], name: "string2"}
// ]
var key = data.environment[env].name;
$scope.dict[key] = data.environment[env].hosts;
}
console.log($scope.envDict)
// in the console:
// Object {string1: Array[2], string2: Array[2]}
}).error(function(data){
console.error(data);
})
};
$scope.getEnv = function(host) {
for (key in $scope.dict) {
// never gets this far because $scope.dict is now = {}
for (value in $scope.dict[key]) {
if ($scope.dict[key][value] === host) {
$scope.env = key;
}
}
}
};
$scope.loadDict();
$scope.getEnv("host1");
}
I can manually call each of these methods and get the results I want from the console, using the MY_SCOPE variable. If I hard-code the dictionary, it works. If I console.log $scope.dict from anywhere in the code except from inside of the $scope.getEnv function, I get the result I expect. As soon as $scope.getEnv is involved, $scope.dict = {}.
I've tried hard-coding the keys into the dictionary. I've tried moving the definition around in the code. I've tried exporting the loadDict method into a factory. All to no avail. Ideas?
The $http.get call in $scope.loadDict is asynchronous. getEnv is getting called before your dictionary has been loaded. You need to call getEnv once that data has come back.
Have loadDict return the $http.getcall which will give you a promise. You can then chain on to that promise a success callback.
You should also put your $http calls in some sort of service to do it the 'angular' way :)
Try this instead:
$scope.loadDict = function() {
return $http.get('api/call/').
success(function(data){
for (env in data.environment) {
var key = data.environment[env].name;
$scope.dict[key] = data.environment[env].hosts;
}
console.log($scope.envDict)
// in the console:
// Object {string1: Array[2], string2: Array[2]}
}).error(function(data){
console.error(data);
})
};
$scope.loadDict().then(function(result){
$scope.getEnv("host1");
}
Your problem is that you didn't deal with the fact that loadDict is async internally.
One way to solve this is to wait for it to complete by returning a promise from it and waiting for that promise to be resolved.
There are other ways to go about this, but this is probably one of the ways that is closest to what you already have:
// inject $q so you can make a promise
function AppController($scope, $http, $q) {
window.MY_SCOPE = $scope;
$scope.env = "Local";
$scope.dict = {};
$scope.loadDict = function() {
// set up the deferred response
var deferred = $q.defer();
$http.get('api/call/').
success(function(data){
for (env in data.environment) {
// data.environment = array of objects
// [
// {hosts: ["host1", "host2"], name: "string1"},
// {hosts: ["host1", "host2"], name: "string2"}
// ]
var key = data.environment[env].name;
$scope.dict[key] = data.environment[env].hosts;
}
console.log($scope.envDict)
// in the console:
// Object {string1: Array[2], string2: Array[2]}
// all is well so resolve the promise
deferred.resolve();
}).error(function(data){
console.error(data);
// reject the promise
deferred.reject(data);
})
return deferred.promise;
};
$scope.getEnv = function(host) {
for (key in $scope.dict) {
// never gets this far because $scope.dict is now = {}
for (value in $scope.dict[key]) {
if ($scope.dict[key][value] === host) {
$scope.env = key;
}
}
}
};
$scope.loadDict().then(
function () {
$scope.getEnv("host1");
},
function (err) {
// whatever you want to do if the loadDict function failed to do its job
}
);
}
$scope.getEnv() is being called before $http.get() has returned data. You need to call $scope.getEnv() within the $http.get().success() block, like so:
$scope.loadDict = function() {
$http.get('api/call/').success(function (data) {
for (env in data.environment) {
var key = data.environment[env].name;
$scope.dict[key] = data.environment[env].hosts;
}
$scope.getEnv("host1");
}).error(function(data){
console.error(data);
});
};
You need to treat things asynchronously . success is an asynchronous callback while getEnv is synchronous.
The solution in this case is to define a promise in loadDict and resolve it on success call.
Then , in the controller getEnv method you would write code after promise is resolved:
Roughly the code will be like this, I have not tested it, just wrote to give you idea:
$scope.loadDict = function() {
var deferred = $q.defer(); // to define a promise
$http.get('api/call/').
success(function(data){
deferred.resolve(data);//resolve the promise on success
}
}).error(function(data){
console.error(data);
})
return deferred.promise;//return promise
};
$scope.getEnv = function(host) {
$scope.loadDict().then(
function(data) {
for (env in data.environment) {
// data.environment = array of objects
// [
// {hosts: ["host1", "host2"], name: "string1"},
// {hosts: ["host1", "host2"], name: "string2"}
// ]
var key = data.environment[env].name;
$scope.dict[key] = data.environment[env].hosts;
for (key in $scope.dict) {
// never gets this far because $scope.dict is now = {}
for (value in $scope.dict[key]) {
if ($scope.dict[key][value] === host) {
$scope.env = key;
}
}
}
});
};

How do I mock a service that returns promise in AngularJS Jasmine unit test?

I have myService that uses myOtherService, which makes a remote call, returning promise:
angular.module('app.myService', ['app.myOtherService'])
.factory('myService', [
myOtherService,
function(myOtherService) {
function makeRemoteCall() {
return myOtherService.makeRemoteCallReturningPromise();
}
return {
makeRemoteCall: makeRemoteCall
};
}
])
To make a unit test for myService I need to mock myOtherService, such that its makeRemoteCallReturningPromise method returns a promise. This is how I do it:
describe('Testing remote call returning promise', function() {
var myService;
var myOtherServiceMock = {};
beforeEach(module('app.myService'));
// I have to inject mock when calling module(),
// and module() should come before any inject()
beforeEach(module(function ($provide) {
$provide.value('myOtherService', myOtherServiceMock);
}));
// However, in order to properly construct my mock
// I need $q, which can give me a promise
beforeEach(inject(function(_myService_, $q){
myService = _myService_;
myOtherServiceMock = {
makeRemoteCallReturningPromise: function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
return deferred.promise;
}
};
}
// Here the value of myOtherServiceMock is not
// updated, and it is still {}
it('can do remote call', inject(function() {
myService.makeRemoteCall() // Error: makeRemoteCall() is not defined on {}
.then(function() {
console.log('Success');
});
}));
As you can see from the above, the definition of my mock depends on $q, which I have to load using inject(). Furthermore, injecting the mock should be happening in module(), which should be coming before inject(). However, the value for the mock is not updated once I change it.
What is the proper way to do this?
I'm not sure why the way you did it doesn't work, but I usually do it with the spyOn function. Something like this:
describe('Testing remote call returning promise', function() {
var myService;
beforeEach(module('app.myService'));
beforeEach(inject( function(_myService_, myOtherService, $q){
myService = _myService_;
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.callFake(function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
return deferred.promise;
});
}
it('can do remote call', inject(function() {
myService.makeRemoteCall()
.then(function() {
console.log('Success');
});
}));
Also remember that you will need to make a $digest call for the then function to be called. See the Testing section of the $q documentation.
------EDIT------
After looking closer at what you're doing, I think I see the problem in your code. In the beforeEach, you're setting myOtherServiceMock to a whole new object. The $provide will never see this reference. You just need to update the existing reference:
beforeEach(inject( function(_myService_, $q){
myService = _myService_;
myOtherServiceMock.makeRemoteCallReturningPromise = function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
return deferred.promise;
};
}
We can also write jasmine's implementation of returning promise directly by spy.
spyOn(myOtherService, "makeRemoteCallReturningPromise").andReturn($q.when({}));
For Jasmine 2:
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue($q.when({}));
(copied from comments, thanks to ccnokes)
describe('testing a method() on a service', function () {
var mock, service
function init(){
return angular.mock.inject(function ($injector,, _serviceUnderTest_) {
mock = $injector.get('service_that_is_being_mocked');;
service = __serviceUnderTest_;
});
}
beforeEach(module('yourApp'));
beforeEach(init());
it('that has a then', function () {
//arrange
var spy= spyOn(mock, 'actionBeingCalled').and.callFake(function () {
return {
then: function (callback) {
return callback({'foo' : "bar"});
}
};
});
//act
var result = service.actionUnderTest(); // does cleverness
//assert
expect(spy).toHaveBeenCalled();
});
});
You can use a stubbing library like sinon to mock your service. You can then return $q.when() as your promise. If your scope object's value comes from the promise result, you will need to call scope.$root.$digest().
var scope, controller, datacontextMock, customer;
beforeEach(function () {
module('app');
inject(function ($rootScope, $controller,common, datacontext) {
scope = $rootScope.$new();
var $q = common.$q;
datacontextMock = sinon.stub(datacontext);
customer = {id:1};
datacontextMock.customer.returns($q.when(customer));
controller = $controller('Index', { $scope: scope });
})
});
it('customer id to be 1.', function () {
scope.$root.$digest();
expect(controller.customer.id).toBe(1);
});
using sinon :
const mockAction = sinon.stub(MyService.prototype,'actionBeingCalled')
.returns(httpPromise(200));
Known that, httpPromise can be :
const httpPromise = (code) => new Promise((resolve, reject) =>
(code >= 200 && code <= 299) ? resolve({ code }) : reject({ code, error:true })
);
Honestly.. you are going about this the wrong way by relying on inject to mock a service instead of module. Also, calling inject in a beforeEach is an anti-pattern as it makes mocking difficult on a per test basis.
Here is how I would do this...
module(function ($provide) {
// By using a decorator we can access $q and stub our method with a promise.
$provide.decorator('myOtherService', function ($delegate, $q) {
$delegate.makeRemoteCallReturningPromise = function () {
var dfd = $q.defer();
dfd.resolve('some value');
return dfd.promise;
};
});
});
Now when you inject your service it will have a properly mocked method for usage.
I found that useful, stabbing service function as sinon.stub().returns($q.when({})):
this.myService = {
myFunction: sinon.stub().returns( $q.when( {} ) )
};
this.scope = $rootScope.$new();
this.angularStubs = {
myService: this.myService,
$scope: this.scope
};
this.ctrl = $controller( require( 'app/bla/bla.controller' ), this.angularStubs );
controller:
this.someMethod = function(someObj) {
myService.myFunction( someObj ).then( function() {
someObj.loaded = 'bla-bla';
}, function() {
// failure
} );
};
and test
const obj = {
field: 'value'
};
this.ctrl.someMethod( obj );
this.scope.$digest();
expect( this.myService.myFunction ).toHaveBeenCalled();
expect( obj.loaded ).toEqual( 'bla-bla' );
The code snippet:
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.callFake(function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
return deferred.promise;
});
Can be written in a more concise form:
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue(function() {
return $q.resolve('Remote call result');
});

Categories