Jasmine Expected Spy to have been called - angularjs

Here is my angular factory written in typescript:
export class DataService {
constructor () {
this.setYear(2015);
}
setYear = (year:number) => {
this._selectedYear =year;
}
}
Here is my test file.
import {DataService } from ' ./sharedData.Service';
export function main() {
describe("DataService", () => {
let service: DataService;
beforeEach(function () {
service = new DataService();
});
it("should initialize shared data service", () => {
spyOn(service, "setYear");
expect(service).toBeDefined();
expect(service.setYear).toHaveBeenCalled(2015);
});
});
}
When I run the file the test failing saying that
**Expected spy setSelectedCropYear to have been called.
Error: Expected spy setSelectedCropYear to have been called.**
I am not able to figure what is wrong. Can anyone tell me what is wrong with the test please.

The problem is you are setting up the spy too late. By the time you mount the spy on service, it has already been constructed and setYear has been called. But you obviously can not mount the spy on service before it is constructed.
One way around this is to spy on DataService.prototype.setYear. You can make sure it was called by the service instance asserting that
Dataservice.prototype.setYear.calls.mostRecent().object is service.

Fixed the issue here is the updated Test.
import {DataService } from ' ./sharedData.Service';
export function main() {
describe("DataService", () => {
let service: DataService;
beforeEach(function () {
service = new DataService();
});
it("should initialize shared data service", () => {
var spy = spyOn(service, "setYear").and.callThrough();
expect(service).toBeDefined();
expect(spy);
expect(service._selectedYear).toEqual(2015);
});
});
}

Related

How to get karma/jasmine unit tests working with a callback after a promise?

I am writing an app in AngularJS 1.5, JavaScript and Cordova.
I want to write a unit test that will check to see if some code was executed after a promise.
Here is my codepen: https://codepen.io/aubz/pen/yrxqxE
I am not sure why but the unit test keeps saying this error:
Expected spy attemptGeoClocking to have been called.
It's strange because the console log prints out so the function is actually being called.
it('if location services are on, proceed', function () {
spyOn(CordovaDiagnostics, 'getLocationServicesStatus').and.callFake(function () {
return Promise.resolve(true);
});
spyOn(Clocking, 'attemptGeoClocking').and.callFake(function () {});
Clocking.geolocationClocking();
expect(Clocking.attemptGeoClocking).toHaveBeenCalled();
});
function geolocationClocking() {
CordovaDiagnostics
.getLocationServicesStatus()
.then(attemptGeoClocking)
.catch(function () {});
}
function attemptGeoClocking() {
console.log(' here ');
}
Basically you're spying on the wrong functions. Let me rename a few things so it's more clear what you're doing:
function Clocking(CordovaDiagnostics) {
return {
geolocationClocking: geolocationClocking,
attemptGeoClockingOUTER: attemptGeoClockingINNER//private API
};
function geolocationClocking() {
CordovaDiagnostics
.getLocationServicesStatus()
.then(attemptGeoClockingINNER)
.catch(function () {});
}
function attemptGeoClockingINNER() {
console.log(' here ');
}
}
And in the test:
spyOn(Clocking, 'attemptGeoClockingOUTER').and.callFake(function () {
console.log('calling fake')
});
As you can see, your code is spying on the OUTER
but geolocationClocking is never calling the OUTER, it's using the INNER:
CordovaDiagnostics
.getLocationServicesStatus()
.then(attemptGeoClockingINNER)
You'll need to rework your code in such a way that it's using the same function internally as to the one you're stubbing in your test.
Here's a working codepen: https://codepen.io/anon/pen/xeyrqy?editors=1111
Note: I've also replaced Promise.resolve with $q.when and added $rootScope.$apply(), this is needed to resolve the promises.
Adding the changes I made here, in case the codepen would ever disappear:
I've changed the factory to a service (while not necessary, I prefer using services in this case):
myApp.service("Clocking", Clocking);
function Clocking(CordovaDiagnostics) {
this.geolocationClocking = function() {
CordovaDiagnostics
.getLocationServicesStatus()
.then(() => this.attemptGeoClocking())
.catch(function () {});
}
this.attemptGeoClocking = function() {
console.log(' here ');
}
}

Angular.js unit test controller with others modules dependencies

Background:
I have an angular.js controller 'GetResultController' with some external dependencies:
angular.module('data-service',[]).service('DataService', DataService);
angular.module('GetResultModule', ['datatables', 'data-service']);
angular.module('GetResultModule')
.controller('GetResultController',
['$q','DTColumnBuilder','DataService', GetResultController]);
function GetResultController($q, DTColumnBuilder, DataService) {
let self = this;
self.getResult = function getResult() {
return 'positive';
}
}
Questions:
ver-1:
describe('testGetResultController', () =>{
beforeEach(angular.mock.module('datatable');
//do i need to create the mock module for 'data-service', and how to do next?
}
)
ver-2:
describe('testGetResultController', () => {
beforeEach(angular.module('datatable',[]);
beforeEach(angular.module('common-service',[]);
let c;
beforeEach(angular.mock.inject(function(_$controller_) {
c = _$controller_('GetResultController'); // is it correct to get back the controller?
}
it('test', ()=> {
expect(c).toBeDefined(); // why c is undefined?
});
})
As I want to draft a unit test to verify 'GetResultController'. How can I initialize the dependencies, mock the object to test the 'getResult' method. Do we have any experience to handle this case.
Many thanks

How to mock Google Analytics function call ga()

I have a service MyService with a function using the ga() event tracking call which I want to test:
angular.module('myModule').factory('MyService', [function() {
var myFunc = function() {
ga('send', 'event', 'bla');
// do some stuff
}
return {
myFunc: myFunc
}
]);
My spec file looks like this:
describe('The MyService', function () {
var MyService,
ga;
beforeEach(function () {
module('myModule');
ga = function() {};
});
beforeEach(inject(function (_MyService_) {
MyService = _MyService_;
}));
it('should do some stuff', function () {
MyService.myFunc();
// testing function
});
});
Running my tests always gives me:
ReferenceError: Can't find variable: ga
The problem is global scope of ga.
The ga variable that you create inside your tests has a local scope and will not be visible to your own service.
By using a global variable (ga) you have made unit testing difficult.
The current option would be to either create a angular service to wrap gaand use that everywhere else. Such service can be mocked too.
The other option is to override the global ga. But this will have side effects.
window.ga=function() {}
After trying different solution I finally fixed with below code.
beforeAll( ()=> {
// (<any>window).gtag=function() {} // if using gtag
(<any>window).ga=function() {}
})
Slightly out of date, but I am trying to leverage ReactGA and mocked creating an event like:
it('should do something...', () => {
const gaSpy = jest.spyOn(ReactGA, 'ga');
someService.functionThatSendsEvent({ ...necessaryParams });
expect(gaSpy).toHaveBeenCalledWith('send', 'event',
expect.objectContaining({/*whatever the event object is supposed to be*/}
);
});
This is helpful if youre sending specific data to an angular/reactjs service which is then sending it to GA.

Unit testing a helper inside a custom provider in angularjs

I have this provider:
angular.module('app').provider('appProvider', function() {
this.$get = Helper;
function Helper() {
function method() {
return true;
};
return {
method: method
};
});
When unit testing it, I can reach appProvider, but not the Helper in unit tests. I've been trying this:
describe('test', function() {
var prov;
beforeEach(angular.mock.module('app', function(appProvider) {
prov = appProvider;
}));
it('provider', inject(function() {
expect(prov.Helper.method()).toEqual(true);
}));
});
And getting this error:
TypeError: 'undefined' is not an object (evaluating 'prov.Helper.method()')
Question is: How do I reach method() in order to evaluate is correct behaviour?
You are trying to test a method of the service that your provider provides, so it seems a bit roundabout to test the provider. Why not just test the service instead?

Mocking Service dependency in angularjs tests

I have a service CurrentUser that has a dependency on StorageFactory. In the constructor of this service, if StorageFactory.get returns a user, I am setting the user to that user, otherwise setting a default user value. Now, I want to test this.
I have managed to make this work, but I am not happy with the approach I am using. I got the inspiration for this approach here.
I have pasted the code below. If you prefer, I have also created a gist here. I have removed the irrelevant part of the code to avoid distraction. This is written using ES6 classes and modules, but that shouldn't make any difference to the tests.
The problem with the approach is that the mock will be used across all the tests, which may not be a bad thing but I want to control that. Is there a way to make this mock take affect only for this test?
One roadblock in finding better approach is that the mock has to be done before angular created the mock CurrentUserModule module. Is there a better way of testing this? I would appreciate any suggestions on this.
Service
import StorageFactoryModule from 'app/common/services/StorageFactory';
class CurrentUser {
constructor(StorageFactory) {
this.storageKey = 'appUser';
this.StorageFactory = StorageFactory;
this.profile = initializeUser.call(this);
function initializeUser() {
var userFromStorage = StorageFactory.get(this.storageKey);
if (userFromStorage != null) {
return userFromStorage;
} else {
// return default user
}
}
}
// more methods, that are removed for brevity
}
CurrentUser.$inject = ['StorageFactory'];
export default angular.module('CurrentUserModule', [StorageFactoryModule.name])
.service('CurrentUser', CurrentUser);
Test
import angular from 'angular';
import 'angular-mocks';
import './CurrentUser';
describe('CurrentUser', function () {
"use strict";
var user = {userid: 'abc', token: 'token'};
var storageFactoryMock = {
get: function (key) {
return user;
},
put: function (key, newUser) {
user = newUser;
},
remove: function (key) {
user = undefined;
}
};
beforeEach(function () {
angular.module('StorageFactoryModule')
.value('StorageFactory', storageFactoryMock);
angular.mock.module('CurrentUserModule');
});
it('should Initialize User from local storage if already exists there', inject(function (CurrentUser) {
expect(CurrentUser.profile).toEqual(user);
}))
});
I have managed to find a better way as below. I was initially doing it the wrong way (feels stupid now). Rather than creating a mock module, I was creating a real module that was overriding the original one, and that's why it was impacting all tests. Now, I am creating a mock module and using $provide to override StorageFactory, which will impact only the current suite.
beforeEach(function () {
angular.mock.module('StorageFactoryModule');
module(function($provide) {
$provide.value('StorageFactory', storageFactoryMock);
});
angular.mock.module('CurrentUserModule');
});
EDIT: Refactored my code and made it more flexible by creating a function that accepts a user as parameter and creates modules based on the user passed.
var correctUser = {userid: 'abc', token: 'token'};
var defaultUser = {userid: '', token: ''};
function createStorageFactoryMock(userInStorage) {
return {
get: function () {
return userInStorage;
},
put: function (key, newUser) {
userInStorage = newUser;
},
remove: function () {
userInStorage = undefined;
}
}
}
function CreateUserModule(user = correctUser) {
angular.mock.module('StorageFactoryModule');
module(function ($provide) {
$provide.value('StorageFactory', createStorageFactoryMock(user));
});
angular.mock.module('CurrentUserModule');
}
Now in my tests, I can mock different module for different scenarios, and write my tests accordingly. Any feedback is welcome.
it('should Initialize User from storageFactory if already exists in storage', function () {
CreateUserModule();
inject(function (CurrentUser) {
expect(CurrentUser.profile).toEqual(correctUser);
});
});
it('should Initialize default user if user not present in storage', function () {
CreateUserModule(null);
inject(function (CurrentUser) {
expect(CurrentUser.profile).toEqual(defaultUser);
});
});

Resources