How to spyOn a service to test it -- AngularJS/Jasmine - javascript

Have tried everything I've found on the internet to make this work with no success. Trying to test a function in my service, but according to my coverage I'm never accessing it. Any help would be greatly appreciated :)
Service:
'use strict';
angular.module('Service').service('configService', function(
$rootScope, $http) {
var configObj = null;
return {
getConfig: function() {
if (configObj != null) {
console.log("returning cached config");
return configObj;
}
else {
return $http.get('conf.json').then(function(res) {
$http.get(res.confLocation).then(function(
locationResponse) {
configObj = locationResponse;
$rootScope.configObj = configObj;
console.log($rootScope.configObj);
return configObj;
});
});
}
}
};
});
getConfig is never being accessed in the tests I've tried.
ServiceTests:
'use strict';
describe('Service: configService', function() {
// load the controller's module
beforeEach(module('Service'));
var configService, $httpBackend, results, tstLocation, tstRes;
var tstConfig = {
"confLocation": "local-dev-conf.json"
};
var tstConfigEmpty = {};
var tstConfigObjEmpty = {};
var tstConfigObj = {
"AWS": {
"region": "us-east-1",
"endpoint": "http://localhost:8133"
}
};
// Initialize the controller and a mock scope
beforeEach(inject(function(_configService_, _$httpBackend_) {
inject(function($rootScope) {
$rootScope.USERNAME = 'TESTER';
$rootScope.configObj = tstConfigObj;
$rootScope.locationResponse = tstLocation;
$rootScope.res = tstRes;
});
configService = _configService_;
$httpBackend = _$httpBackend_;
//Problem here??
spyOn(configService, 'getConfig').and.callFake(function() {
return {
then: function() {
return "something";
}
};
});
}));
it('should return a promise', function() {
expect(configService.getConfig().then).toBeDefined();
});
it('should test backend stuff', inject(function() {
results = configService.getConfig(tstConfig);
$httpBackend.expectGET('conf.json').respond(tstConfig);
$httpBackend.expectGET('local-dev-conf.json').respond(tstConfigObj);
$httpBackend.flush();
}));
//Thanks Miles
it('should check if it was called', inject(function() {
results = configService.getConfig().then();
expect(configService.getConfig).toHaveBeenCalled();
});
// console.log(results);
}));
it('should check for a null configObj', inject(function() {
results = configService.getConfig(tstConfigObjEmpty).then(function() {
expect(results).toBe(null);
});
// console.log(results);
// console.log(tstConfigObj);
}));
it('should check for a non-null configObj', inject(function() {
results = configService.getConfig(tstConfigObj).then(function() {
// Any string is accepted right now -- Why??
expect(results).toEqual("returning cached config");
expect(results).toBe("returning cached config");
expect(results).toBe("your mom"); // SHOULDN'T BE WORKING BUT DOES
expect(results).toEqual("Object{AWS: Object{region: 'us-east-1', endpoint: 'http://localhost:8133'}}");
expect(results).toBe("Object{AWS: Object{region: 'us-east-1', endpoint: 'http://localhost:8133'}}");
});
// console.log(results);
// console.log(tstConfigObj);
}));
it('should check for null file', inject(function() {
results = configService.getConfig(tstConfigEmpty).then(function() {
expect(results).toEqual(null);
expect(results).toBe(null);
});
}));
it('should test a valid file', inject(function() {
results = configService.getConfig(tstConfig).then(function() {
expect(results).not.toBe(null);
expect(results).toEqual("Object{confLocation: 'local-dev-conf.json'}");
})
});
I think I'm using spyOn wrong, or not accessing getConfig in my tests properly. Thoughts?
EDIT: Here is my code coverage
EDIT 2: Changed test 3 thanks to a problem found by Miles, still no update on test coverage though. Something is wrong with my spyOn logic as Amy pointed out. I shouldn't be using callFake it seems?
EDIT 3: Got it accessing the function now thanks to Miles. Had to change my spyOn to:
spyOn(configService, 'getConfig').and.callThrough();
then add the test case:
results = configService.getConfig(tstConfig).then();
expect(configService.getConfig).toHaveBeenCalled();
Coverage now (still needs work)

You're calling a fake instead of the function. So the logic inside of the function does not get called.

You have an issue here:
results = configService.getConfig(tstConfigObj).then(function() {
expect(results).toHaveBeenCalled();
expect(results).toHaveBeenCalledWith(tstConfigObj);
});
getConfig takes no parameters, and neither does then. Omitting these errors, results is assigned the string "something" from then. Even if the expect statements fire, you seem to be testing if a string has been called. Try this instead:
results = configService.getConfig().then();
expect(configService.getConfig).toHaveBeenCalled();

What version of Jasmine are you using? The and.callFake syntax was added in Jasmine 2.0. Maybe the test suite just needs to point to the new version.
Jasmine 1.3 Docs
Jasmine 2.0 Docs

Related

Jasmine and angular mocks : mocking a service that handles local storage

I have one service called wd$cache, that is basically a wrapper for localStorage.setItem and get.item.
Now I'm trying to test a controller that uses that service to achieve a certain result. The main problem is that I have an IF statement that gets triggered only if you have localstorage set already which is driving me nuts! (we are doing TDD here)
SERVICE
(function () {
angular
.module('hub')
.controller('promotionNotificationCtrl', promotionNotificationCtrl);
promotionNotificationCtrl.$inject = [
'hub$promotions',
'hub$client',
'wd$cache'
];
function promotionNotificationCtrl(
hub$promotions,
hub$client,
wd$cache) {
var vm = this;
activate();
//////////
function activate () {
hub$promotions.get(hub$client.brand, hub$client.subbrand).success(function (data) {
if (!wd$cache.get('hub$notification')) {
wd$cache.add('before', 123);
} else {
wd$cache.add('after', 321);
}
});
}
}
})();
TEST
describe('The promotion notification controller', function () {
var controller,
hub$client,
$httpBackend,
wd$cache,
mockData = [{
"foo": "bar"
},
{
"faa": "boo"
}];
beforeEach(module('hub'));
beforeEach(module('wired.core'));
beforeEach(module(function ($provide) {
hub$client = {
brand: 'bw',
subbrand: 'plus'
};
wd$cache = {
add: function () {
},
get: function () {
}
};
$provide.value('hub$client', hub$client);
$provide.value('wd$cache', wd$cache);
spyOn(wd$cache, 'add');
}));
beforeEach(inject(function ($controller, _$httpBackend_, _hub$promotions_) {
controller = $controller('promotionNotificationCtrl');
$httpBackend = _$httpBackend_;
hub$promotions = _hub$promotions_;
// request
$httpBackend.expectGET("/umbraco/api/promotions/get/?brand=bw&lang=en&subbrand=plus").respond(200, mockData);
$httpBackend.flush();
}));
it('should attempt to add a cache with a "before" key if no previous "hub$notification" cache was found', function () {
expect(wd$cache.add).toHaveBeenCalledWith('before', 123); //WORKING
})
it('should attempt to add a cache with a "after" key if a previous "hub$notification" cache was found', function () {
localStorage.setItem('hub$notification');
wd$cache.add('hub$notification');
expect(wd$cache.add).toHaveBeenCalledWith('after', 123); // NOT WORKING
// CANT GET THROUGH THE IF STATEMENT
})
});
Basically I can never get to 'Test Cases' after BeforeEach block, whatever I do. I've tried everything, since mocking it to use actual storage.
Any ideas?
You can provide a mock implementation that is already filled with some data:
var cache = {};
beforeEach(module(function ($provide) {
// ...
wd$cache = {
add: function (key, value) {
cache[key] = value;
},
get: function (key) {
return cache[key];
}
};
// add initial data here or in the individual tests, e.g.
// ...
}));
To set up the cache properly for a specific testcase you can use the cache field like this:
cache['hub$notification'] = 'whatever value makes sense here';
Of course you can also do this in beforeEach.
Currently you are trying to do it like this:
wd$cache.add('hub$notification');
expect(wd$cache.add).toHaveBeenCalledWith('after', 123);
This is problematic for two reasons:
You are not updating the cache because you are spying on the add method without .andCallThrough(). You should fix this (add .andCallThrough() after spy creation) otherwise updates from the controller will not affect the cache.
The spy records your call instead. You don't want this for setup code because it makes subsequent assertions more complicated.

Jasmine custom matcher with Protractor for checking browser title

I am writing a Jasmine custom matcher to use in a Protractor spec and I want to check that the browser title is equal to some string. I am unable to get this code to work properly and after spending hours debugging it, I can only assume that the browser object is not being accessed inside the matcher function as I expect it to be. When I modify the matcher function to accept browse.getTitle() as the actual argument then it works fine, which leads me to my assumption. Can anyone find the issue here and explain it to me?
beforeEach(function() {
jasmine.addMatchers({
toBeOnPage: function(util, customEqualityTesters) {
return {
compare: function(actual, expected) {
var result = {};
result.pass = actual.getTitle() === expected.title;
return result;
}
};
}
});
});
var homepage = { url: 'Homepage URL', title: 'Homepage Title' };
describe('regression:', function() {
it('homepage loads successfully', function() {
browser.get('http://localhost/#/home');
expect(browser).toBeOnPage(homepage);
});
});
The problem is that getTitle() returns a promise. Resolve it:
beforeEach(function() {
jasmine.addMatchers({
toBeOnPage: function(util, customEqualityTesters) {
return {
compare: function(actual, expected) {
return {
pass: actual.getTitle().then(function (title) {
return title === expected.title;
});
};
}
};
}
});
});

Jasmine, Parse.com&Promises: Testing functions that call query.get/find

I'm trying to get my head around testing a parse.com backed data service, but have not found a satisfying solution so far. Basically, I want to use karma/jasmine to test a function returning a promise.
I'm spying on the query.get() method to intercept and return an object that was created in the implementation of the jasmine spec. However, when the spy returns the local object, the success path of the query.get options argument is not executed. Instead, the to-be-tested function directly returns the unresolved promise, which is not what I want. Basically, I want to test the success path (and the error path) of the data service.
Here's the code:
to-be-tested parse.com data services
angular.module('app.services',[])
.factory('AppServices',function(){
var doFunction1 = function(){
var promise = new Parse.Promise();
var ParseObjectClass = Parse.Object.extend('ParseObject');
var query = new Parse.Query(ParseObjectClass);
query.get('abc1234',{
success: function(result){
promise.resolve(result);
},
error: function(result, error){
promise.reject(error);
}
});
return promise;
}
return {
function1: doFunction1
}
});
jasmine spec
describe('MyServices Tests', function() {
var AppServices;
var scope;
beforeEach(function() {
module('app.services');
});
beforeEach(inject(function($rootScope, AppServices) {
AppServices = AppServices;
scope = $rootScope.$new();
Parse.initialize("key1", "key2");
var user = new Parse.User({
id: 'abc1234',
});
var ParseObjectClass = Parse.Object.extend('ParseObject');
var obj1 = new ParseObjectClass({
id: 'xyc789'
});
spyOn(Parse.Query.prototype, 'get')
.and.callFake(function(options) {
return Parse.Promise.as(obj1);
});
}));
it('parseMockTest', function() {
var result = AppServices.function1(2);
console.log(JSON.stringify(result));
});
});
This is the result from the karma log:
LOG: '{"_resolved":false,"_rejected":false,"_resolvedCallbacks":[],"_rejectedCallbacks":[]}'
I would have expected that the returned promise is resolved.
Any ideas/hints on how to test functions that return promises?

Testing rejected promise in Jasmine

I am building AngularJS application and I am trying to unit test it. What I want to test is rejected scenario.
This is the service I am trying to test:
.factory('LoginService',['Login', 'consoleService', function(Login, consoleService){
return {
getActiveUser: function() {
return Login.query({}).$promise.then(
function (users) {
return users[0];
},
function (error) {
consoleService.print(error);
}
);
}
};
}]);
I am trying to write unit test for rejected scenario (error occurred). And I want to be sure that print function is gonna be called when some error occurres. I tried different examples, but neither one didnĀ“t work. Does anyone have idea what is correct way to test it?
The Mock rejection service has already been discussed in the below link
How to use mock $httpBackend to test error branch
You could mock the Login service, then depending on a variable have it call the pass or fail functions.
var failLogin;
beforeEach(module(function ($provide) {
failLogin = false;
var mockLogin = {
query: function(data) {
return {
$promise: {
then: function(pass, fail) {
if (failLogin) {
fail('failure data');
} else {
pass('pass data');
}
}
}
}
}
}
$provide.value('login', mockLogin);
}
it('should pass', function(){
LoginService.getActiveUser();
// should pass
});
it('should fail', function(){
failLogin = true;
LoginService.getActiveUser();
// should fail
});

AngularJS Unit Testing with PouchDB Service Promises

I have a factory, which I am attempting to unit test, with an injected PouchDB wrapper. The issue I am running into is that I have the mock PouchDB service returning a promise and while the promise resolves (I can console.log out the successCb and it looks correct), the karma expect statements within the successCb function where the correct console.log output is, are not running.
I am not sure why the console.log output works fine but the expect statements do not execute, any help on this matter would be greatly appreciated!
The following are the relevant snippets of code:
[ Unit Testing Setup ]
var mockCalendarsResource, mockPouchDB;
var mockPouchDB = function(name) {
this.name = name;
this.post = function(newCalendar, successCb, errorCb) {
var promise = new Promise(function(successCb, errorCb) {
newCalendar._id = 2;
successCb(newCalendar);
});
return promise;
};
}
beforeEach(function() {
module(function($provide) {
$provide.value('Pouch', mockPouchDB);
});
angular.mock.inject(function($injector) {
mockCalendarsResource = $injector.get('Calendar');
});
});
[ Karma Unit Test Code ]
describe('The calendar resource function create', function() {
it('should create a new calendar', inject(function(Calendar) {
var result = mockCalendarsResource.create({
name: "Testing"
},
function(response) {
//console.log here works correctly //
//These are the expect statements not functioning //
expect(response._id).toBe(2);
expect(response.name).toBe('Testing');
// Could put any expectation here and it will pass or not be checked //
}, function(err) {
});
}));
});
I believe you should be using Jasmine 2.0's done() to signal that the test has finished. If you don't then the async tests will not finish.

Categories

Resources