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.
Related
I've looked at the documentation for angular.mock.module and a couple of examples of others using it but I seem to be running into an issue in my use-case that I don't understand.
I'm running Jasmine (2.4.1) tests with angular (1.4.9) and I have my angular app separated into multiple modules. When I attempt to mock out certain parts of my app for unit testing I want to mock out entire modules (or providers) so that I only expose the pieces I use.
Here is a very simple app that has a main module plunker which depends on plunker.service. plunker.service depends on plunker.constant.
var app = angular.module('plunker', ['plunker.service']);
app.controller('MainCtrl', function($scope, valueService, appService) {
$scope.init = function() {
$scope.appValue = valueService.getValue();
$scope.appIsRunning = appService.getStatus();
};
});
angular.module('plunker.service', ['plunker.constant'])
.service('appService', function(appSettings) {
var vm = this;
vm.getStatus = function () {
if (appSettings.isRunning) {
return true;
} else {
return false;
}
};
})
.service('valueService', function(valueSettings) {
var vm = this;
vm.getValue = function () {
return valueSettings.value;
}
});
angular.module('plunker.constant', [])
.constant('appSettings', { isRunning: true })
.constant('valueSettings', { value: 10 });
In my Jasmine tests I have a beforeEach() that registers my modules using module (aka angular.mock.module).
I have seen 3 ways of using module
string
function with $provide
object
You can see below that I use the module('plunker') (string) to register my main module and I have 3 ways of mocking out my appSettings constant (A, B, C). You will notice that the function with $provide.constant works fine but function with $provide.value does not and object does not.
beforeEach(function() {
module('plunker');
function useFunction(typeofProvider) {
module(function($provide) {
$provide[typeofProvider]('appSettings', { isRunning: false });
});
}
function useObject() {
module({
appSettings: { isRunning: false }
});
}
// A. THIS WORKS! //
useFunction('constant');
// B. THIS DOES NOT //
// useFunction('value');
// C. THIS ALSO DOES NOT!! //
// useObject();
inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
ctrl = $controller('MainCtrl', {
$scope: $scope
});
});
});
I have also seen people use the following syntax...
beforeEach(function() {
var mockService = function () {
var mockValue = 10;
this.value = mockValue;
};
// D.
module('a.module.name', function newProviders($provide){
$provide.service('realService', mockService);
});
});
My questions
In my test code, why does A. work but B. and C. do not?
Is D. equivalent to calling module('a.module.name'); followed by module(function newProviders($provide) { ... });? Does placing both in the same module() call have any special effects on how things are registered or is it just a shorthand? (based on the documentation it should be a shorthand)
Related to Jasmine, specifically, do all beforeEach() calls run in the same top-to-bottom order with every execution?
Here is my plunker for the above app and jasmine code
Thanks
This happens because of how Angular injector works. In fact, there are two different injectors in Angular. The one (available as $injector in config blocks) deals with service providers. Another one (available as $injector anywhere else) deals with service instances. Providers and instances are cached and stored internally.
$provide.constant('service') creates both provider and instance of name 'service' at call time.
All other types of services are lazily instantiated. They create 'serviceProvider' provider at call time, but 'service' instance is created on the first injection.
Since Angular service instance is a singleton, it refers to instance cache before the instantiation. If the instance is in the cache, it is reused and not instantiated. constant service instance is eagerly instantiated, so only another constant can override the instance.
Object properties in angular.mock.module are shortcuts for $provide.value, and useObject() equals to useFunction('value') in this example.
As long as module order stays the same,
module('a.module.name', function ($provide) { ... });
is indeed a shortcut for
module('a.module.name');
module(function ($provide) { ... });
Due to the fact that appSettings object isn't used in config blocks (the primary use of constant service), it is more convenient to make it value.
I wish to reuse my mocks instead of having to set them up in every unit test that has them as dependency. But I'm having a hard time figuring out how to inject them properly.
Here's my attempt at unit test setup, which of course fails because ConfigServiceMockProvider doesn't exist.
describe('LoginService tests', function () {
var LoginService;
beforeEach(module('mocks'));
beforeEach(module('services.loginService', function ($provide, _ConfigServiceMock_) {
$provide.value("ConfigService", _ConfigServiceMock_);
/* instead of having to type e.g. everywhere ConfigService is used
* $provide.value("ConfigService", { 'foobar': function(){} });
*/
});
beforeEach(inject(function (_LoginService_) {
LoginService = _LoginService_;
});
}
ConfigServiceMock
angular.module('mocks').service('ConfigServiceMock', function() {
this.init = function(){};
this.getValue = function(){};
}
I realize I probably could have ConfigServiceMock.js make a global window object, and thereby not needing to load it like this. But I feel there should be a better way.
Try something like this:
describe('Using externally defined mock', function() {
var ConfigServiceMock;
beforeEach(module('mocks'));
beforeEach(module('services.configService', function($provide) {
$provide.factory('ConfigService', function() {return ConfigServiceMock;});
}));
beforeEach(module('services.loginService'));
beforeEach(inject(function (_ConfigServiceMock_) {
ConfigServiceMock = _ConfigServiceMock_;
}));
// Do not combine this call with the one above
beforeEach(inject(function (_LoginService_) {
LoginService = _LoginService_;
}));
it('should have been given the mock', function() {
expect(ConfigServiceMock).toBeDefined('The mock should have been defined');
expect(LoginService.injectedService).toBeDefined('Something should have been injected');
expect(LoginService.injectedService).toBe(ConfigServiceMock, 'The thing injected should be the mock');
});
});
According to this answer, you have to put all of your calls to module before all of your calls to inject.
This introduces a bit of a catch-22 because you have to have the reference to your ConfigServiceMock (via inject) into the spec before you can set it on the LoginService (done in the module call)
The work-around is to set an angular factory function as the ConfigService dependency. This will cause angular to lazy load the service, and by that time you will have received your reference to the ConfigServiceMock.
I have a situation where I want to add services inside a module, as I may not know what they are beforehand. From looking at the docs, it seems that the only way to do this (without global scope) is with Angular's $injector service. However, it seems that this service is not mockable, which makes sense as it is the way Angular itself gets the dependencies, which are still important even in testing.
Essentially, I am emulating NodeJS's passport module. I want to have something like a keychain, where you add or remove an account during runtime. So far, I have this:
angular.module('myModule').factory('accounts', function($injector) {
return {
add: function(name) {
if(!$injector.has(name) {
$log.warn('No Angular module with the name ' + name + ' exists. Aborting...');
return false;
}
else {
this.accounts[name] = $injector.get(name);
return true;
}
},
accounts: []
};
});
However, whenever I try to mock the $injector function in Jasmine, like this:
describe('accounts', {
var $injector;
var accounts;
beforeEach(function() {
$injector = {
has: jasmine.createSpy(),
get: jasmine.createSpy()
};
module(function($provide) {
$provide.value('$injector', $injector);
});
module('ngMock');
module('myModule');
inject(function(_accounts_) {
accounts = _accounts_;
});
});
describe('get an account', function() {
describe('that exists', function() {
beforeEach(function() {
$injector.has.and.returnValue(true);
});
it('should return true', function() {
expect(accounts.add('testAccount')).toEqual(true);
});
});
describe('that doesn't exist', function() {
beforeEach(function() {
$injector.has.and.returnValue(false);
});
it('should return true', function() {
expect(accounts.add('testAccount')).toEqual(false);
});
});
});
});
the 2nd test fails because the accounts service is calling the actual $injector service, and not the mock. I can confirm this by calling $injector.get or $injector.has during the test or in the service itself.
What should I do? There seems to be no other way to add new dependencies, but this is exactly what I want to do. Am I wrong? Is there in fact another way to do this, without using $injector?
Assuming I am right, and there is no other way to do what I want to do, how should I go about testing this function? I could just trust that the $injector service does its job, but I still want to mock it for the tests. I could manually add the dependencies during the inject function, but that doesn't replicate the actual behavior. I could just not test the function, but then I wouldn't be testing the function.
Looking at an example from Mastering Web Applications in AngularJS:
angular.module('archive', [])
.factory('notificationsArchive', function () {
var archivedNotifications = [];
return {
archive:function (notification) {
archivedNotifications.push(notification);
},
getArchived:function () {
return archivedNotifications;
}};
});
Then, the module's test:
describe('notifications archive tests', function () {
var notificationsArchive;
beforeEach(module('archive'));
beforeEach(inject(function (_notificationsArchive_) {
notificationsArchive = _notificationsArchive_;
}));
it('should give access to the archived items', function () {
var notification = {msg: 'Old message.'};
notificationsArchive.archive(notification);
expect(notificationsArchive.getArchived())
.toContain(notification);
});
});
What's going on in the second beforeEach(inject ...?
beforeEach(inject(function (_notificationsArchive_) {
notificationsArchive = _notificationsArchive_;
}));
That's just saying before each test, get an instance of notificationsArchive. It then assigns that instance to a variable that can be used in the actual test case. The underscores around notificationsArchive are just syntactic sugar so you don't have to come up with another name for the variable that your test users.
It is injecting the notificationsArchive service into a function that assigns that service to the local variable "notificationsArchive" before each test. The underscores in the name are ignored.
https://docs.angularjs.org/api/ngMock/function/angular.mock.inject
How do you write tests for something like FabricJS in a directive and service?
Example app: http://fabricjs.com/kitchensink/
I have been trying but I'm not making much progress without really bad hacks.
I want to integrate this service and directive into my https://github.com/clouddueling/angular-common repo so others can use this powerful library.
My scenario:
I'm trying to test my module that contains a service and directive. Those link my app to FabricJS. I'm having issues mocking the global fabric var that is created when you include the js file. I'm assuming then I spy on the var containing the fabric canvas.
I just need to confirm that my service is interacting with fabric correctly. I'm having trouble mocking/stubbing fabric though.
To win the bounty:
Example of a test I could use with Karma.
It's difficult as you've not provided the code you want to test. However, for testability, I would firstly create a very small factory to return the global fabric object
app.factory('fabric', function($window) {
return $window.fabric;
});
This factory can then be tested by injecting a mock $window, and checking that its fabric property is returned.
describe('Factory: fabric', function () {
// load the service's module
beforeEach(module('plunker'));
var fabric;
var fakeFabric;
beforeEach(function() {
fakeFabric = {};
});
beforeEach(module(function($provide) {
$provide.value('$window', {
fabric: fakeFabric
});
}));
beforeEach(inject(function (_fabric_) {
fabric = _fabric_;
}));
it('should return $window.fabric', function () {
expect(fabric).toBe(fakeFabric);
});
});
An example service that then uses this factory is below.
app.service('MyFabricService', function(fabric) {
this.newCanvas = function(element) {
return new fabric.Canvas(element);
}
this.newRectangle = function(options) {
return new fabric.Rect(options);
}
this.addToCanvas = function(canvas, obj) {
return canvas.add(obj);
}
});
You can then test these methods as below. The functions that return 'new' objects can be tested by creating a mock fabric object with a manually created spy that will be called as a constructor, and then using instanceof and toHaveBeenCalledWith to check how its been constructed:
// Create mock fabric object
beforeEach(function() {
mockFabric = {
Canvas: jasmine.createSpy()
}
});
// Pass it to DI system
beforeEach(module(function($provide) {
$provide.value('fabric', mockFabric);
}));
// Fetch MyFabricService
beforeEach(inject(function (_MyFabricService_) {
MyFabricService = _MyFabricService_;
}));
it('should return an instance of fabric.Canvas', function () {
var newCanvas = MyFabricService.newCanvas();
expect(newCanvas instanceof mockFabric.Canvas).toBe(true);
});
it('should pass the element to the constructor', function () {
var element = {};
var newCanvas = MyFabricService.newCanvas(element);
expect(mockFabric.Canvas).toHaveBeenCalledWith(element);
});
The addToCanvas function can be tested by creating a mock canvas object with an 'add' spy.
var canvas;
// Create mock canvas object
beforeEach(function() {
canvas = {
add: jasmine.createSpy()
}
});
// Fetch MyFabricService
beforeEach(inject(function (_MyFabricService_) {
MyFabricService = _MyFabricService_;
}));
it('should call canvas.add(obj)', function () {
var obj = {};
MyFabricService.addToCanvas(canvas, obj);
expect(canvas.add).toHaveBeenCalledWith(obj);
});
This can all be seen in action in this Plunker http://plnkr.co/edit/CTlTmtTLYPwemZassYF0?p=preview
Why would you write tests for an external dependency?
You start by assuming FabricJS just works. It's not your job to test it, and even if it were, you'd have to do byte stream comparison (that's what a canvas is, a stream of bytes interpreted as an image). Testing user input is a whole different thing. Look up Selenium.
Then you write tests for the code that produces the correct input for FabricJS.