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.
Related
Trying to write a jasmine test for the below code...
refreshCacheIfNewVersionIsAvailable();
//Check if a new cache is available on page load and reload the page to refresh app cache to the newer version of files
function refreshCacheIfNewVersionIsAvailable() {
$window.addEventListener('load', function (e) {
$window.applicationCache.addEventListener('updateready', function (e) {
if ($window.applicationCache.status == window.applicationCache.UPDATEREADY) {
// Manifest changed. Now Browser downloadeds a new app cache.
alert(textService.versioning.newVersionMessage);
$window.location.reload(true);
} else {
// Manifest didn't change. Nothing new to server.
}
}, false);
}, false);
}
Your challenge
I assume the challenge you are facing is that you are unable to see how to test the code in the callback functions. You just have to realize that you have access to the callback function when you spy on addEventListener, after the spy is executed in your service under test (refreshCacheIfNewVersionIsAvailable). Since you can get a reference to it, you can execute it, just as if it was the function you were testing.
Sample solution
The following is untested, written off the top of my head, but something along the lines of what I would expect to write if I had to test that code.
describe('refreshCacheIfNewVersionIsAvailable()', function() {
beforeEach(function() {
spyOn($window, 'addEventListener');
});
it('should register a load event handler on the window', function() {
refreshCacheIfNewVersionIsAvailable();
expect($window.addEventListener.calls.count()).toBe(1);
var args = $window.addEventListener.calls.argsFor(0);
expect(args.length).toBe(3);
expect(args[0]).toBe('load');
expect(typeof args[1]).toBe('function');
expect(args[2]).toBe(false);
});
describe('load event', function() {
var loadFunction;
beforeEach(function() {
refreshCacheIfNewVersionIsAvailable();
var args = $window.addEventListener.calls.argsFor(0);
loadFunction = args[1];
spyOn($window.applicationCache, 'addEventListener');
});
it('should register an updateready event handler in the window application cache', function() {
loadFunction();
expect($window.applicationCache.addEventListener.calls.count()).toBe(1);
var args = $window.applicationCache.addEventListener.calls.argsFor(0);
expect(args.length).toBe(3);
expect(args[0]).toBe('updateReady');
expect(typeof args[1]).toBe('function');
expect(args[2]).toBe(false);
});
describe('updateready event', function() {
var updateReadyFunction;
beforeEach(function() {
loadFunction();
var args = $window.applicationCache.addEventListener.calls.argsFor(0);
updateReadyFunction = args[1];
});
it('should reload the window if the status is UPDATEREADY', function() {
// You get the point
});
});
});
});
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.
I made a simple demo of a factory and I am trying to test this using jasmine. I am able to run the test but I am using the spyOn method. I would rather use jasmine.createSpy or jasmine.createSpyObj to do the same test. Could someone help me to refactor my code so that uses these methods instead in my example?
http://plnkr.co/edit/zdfYdtWbnQz22nEbl6V8?p=preview
describe('value check',function(){
var $scope,
ctrl,
fac;
beforeEach(function(){
module('app');
});
beforeEach(inject(function($rootScope,$controller,appfactory) {
$scope = $rootScope.$new();
ctrl = $controller('cntrl', {$scope: $scope});
fac=appfactory;
spyOn(fac, 'setValue');
fac.setValue('test abc');
}));
it('test true value',function(){
expect(true).toBeTruthy()
})
it('check message value',function(){
expect($scope.message).toEqual(fac.getValue())
})
it("tracks that the spy was called", function() {
expect(fac.setValue).toHaveBeenCalled();
});
it("tracks all the arguments of its calls", function() {
expect(fac.setValue).toHaveBeenCalledWith('test abc');
});
})
update
angular.module('app',[]).factory('appfactory',function(){
var data;
var obj={};
obj.getValue=getValue;
obj.setValue=setValue;
return obj;
function getValue(){
return data;
}
function setValue(datavalue){
data=datavalue;
}
}).controller('cntrl',function($scope,appfactory){
appfactory.setValue('test abc');
$scope.message=appfactory.getValue()
})
I have changed your plunkr:
spy = jasmine.createSpy('spy');
fac.setValue = spy;
Edit
In Jasmine, mocks are referred to as spies. There are two ways to
create a spy in Jasmine: spyOn() can only be used when the method
already exists on the object, whereas jasmine.createSpy() will return
a brand new function.
Found the information here. The link has a lot more information about creating spies.
As said in the comments, you have absolutely no need for spies to test such a service. If you had to write the documentation for your service: you would say:
setValue() allows storing a value. This value can then be retrieved by calling getValue().
And that's what you should test:
describe('appfactory service',function(){
var appfactory;
beforeEach(module('app'));
beforeEach(inject(function(_appfactory_) {
appfactory = _appfactory_;
}));
it('should store a value and give it back',function() {
var value = 'foo';
appfactory.setValue(value);
expect(appfactory.getValue()).toBe(value);
});
});
Also, your service is not a factory. A factory is an object that is used to create things. Your service doesn't create anything. It is registered in the angular module using a factory function. But the service itself is not a factory.
I've been trying to write some unit tests for my services which use AngularFire to communicate with Firebase inside an Angular website.
I'm new to AngularJS and so I feel like I'm missing something obvious but couldn't find any great examples online (at least not that spoke to my limited knowledge).
I found some limited docs on MockFirebase https://github.com/katowulf/mockfirebase/tree/master/tutorials and that showed how to pretty much mock out the data so I did that.
For further examples of mockfirebase I looked at the angular fire's unit tests https://github.com/firebase/angularfire/tree/master/tests/unit but that didn't seem to show me the right way.
Here is my service --
app.service('Subscription', function ($firebase, FIREBASE_URL, $q) {
var ref;
var Subcription = {
ref: function () {
if (!ref) ref = new Firebase(FIREBASE_URL + "/subscriptions");
return ref;
},
validateSubscription: function(userId){
var defer = $q.defer();
$firebase(Subcription.ref().child(userId))
.$asObject()
.$loaded()
.then(function (subscription) {
defer.resolve(subscription.valid === true);
});
return defer.promise;
},
recordSubscription: function(userId){
return Subcription.ref().$set(userId, {valid: true});
}
};
return Subcription;
});
Here is the spec file --
describe('Service: subscription', function () {
// load the service's module
beforeEach(module('clientApp'));
// instantiate service
var subscription;
var scope;
beforeEach(inject(function (_Subscription_, $rootScope) {
MockFirebase.override();
subscription = _Subscription_;
scope = $rootScope.$new();
}));
it('allows access when the user id is in the subscription list', function () {
subscription.ref().push({'fakeUser': {valid: true}});
subscription.ref().flush();
var handler = jasmine.createSpy('success');
subscription.validateSubscription('fakeUser').then(handler);
scope.$digest();
expect(handler).toHaveBeenCalledWith(true);
});
});
It seems like the problem is that the promise never gets resolved inside of $asobject.$loaded because that angularfire part isn't happening.
I get the following as a result of the test: 'Expected spy success to have been called with [ true ] but it was never called.'
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.