Mocking an angular service in Protractor - angularjs

I'm trying to use Protractor's addMockModule to insert mock data in my end-2-end test.
My test is supposed to go to a web site, find a button by css-class and click it. The button click calls the function dostuff() in MyService, which fetches data from the backend.
My code so far:
describe('should display validation error', function () {
it('should work', function () {
browser.get('http://my.url');
browser.addMockModule('MyService', function () {
// Fake Service Implementation returning a promise
angular.module('MyService', [])
.value({
dostuff: function () {
return {
then: function (callback) {
var data = { "foo": "bar" };
return callback(data);
}
};
}
});
});
var button = element(by.css('.btn-primary'));
button.click();
browser.sleep(5000);
});
});
The test is accessing the web site and clicking the button. The problem is that real data from the database is displayed, not the mock data.
I followed several threads, like this one: How to mock angular.module('myModule', []).value() in Jasmine/Protractor
However, it seems like the function protractor.getInstance() is deprecated.
Anyone got this working?

Take a look at the unit test for addMockModule(). Try to add the addMockModule statement before you call browser.get()
https://github.com/angular/protractor/blob/673d416b7ef5abd0953da940cfa8cf2a59650df4/spec/basic/mockmodule_spec.js

Related

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.

Testing window.postMessage directive

I'm having trouble testing my directive which enables cross-document messaging by registering a message handler:
.directive('messaging', function ($window, MyService) {
return {
link: function () {
angular.element($window).on('message', MyService.handleMessage);
}
};
})
All I want to unit test is that when this directive is compiled, and window.postMessage('message','*') is called, my message handler should be called:
http://jsfiddle.net/mhu23/L27wqn14/ (including jasmine test)
I'd appreciate your help!
Michael
Your are using original window API, you are not mocking it, so the method postMessage will keep it's asynchronous behavior. Knowing that, tests should be written in an asynchronous way. In JSFiddle you have Jasmine 1.3, so test should look kinda like this:
it('should ....', function () {
var done = false;
spyOn(MyService,'handleMessage').andCallFake(function () {
// set the flag, let Jasmine know when callback was called
done = true;
});
runs(function () {
// trigger async call
$window.postMessage('message','*');
});
waitsFor(function () {
// Jasmine waits until done becomes true i.e. when callback be called
return done;
});
runs(function () {
expect(MyService.handleMessage).toHaveBeenCalled();
});
});
Check the docs about testing async with Jasmine 1.3. And here is a working JSFiddle.
It would be a bit easier in Jasmine 2.x:
it('should ....', function (done) {
spyOn(MyService,'handleMessage').and.callFake(function () {
expect(MyService.handleMessage).toHaveBeenCalled();
done();
});
$window.postMessage('message','*');
});
Also, I have to mention, that you have to change how you add a listener from this
angular.element($window).on('message', MyService.handleMessage);
to that
angular.element($window).on('message', function (e) {
MyService.handleMessage(e);
});
because .on registers a function itself, it won't be used as a method attached to the MyService, so you won't be able to spy on it.

Spy doesn't get called using async jasmine and requirejs

I have a setup of AngularJS application that uses RequireJS to download and register services on-demand. I also use Jasmine for testing. I am trying to test if a function is called in the callback of a require() call that is executed inside of a module definition. Look at the following file I have and want to test:
define(['app'], function(app) {
app.registerService('myService', function($injector) {
this.someMethod = function() {
require(['some-other-file'], function() {
var someOtherService = $injector.get('someOtherService');
console.log("first");
someOtherService.bla();
});
};
});
});
I want to test that when myService.someMethod() is called, someOtherService.bla() is also called. This is my test file:
define(['some-file', 'some-other-file'], function() {
//....
it('should test if someOtherService.bla() is called', function(done) {
inject(function($rootScope, myService, someOtherService) {
spyOn(someOtherService, 'bla');
myService.someMethod();
$rootScope.$digest();
setTimeout(function() {
console.log("second");
done();
}, 500);
expect(someOtherService.bla).toHaveBeenCalled();
});
});
});
The console output shows that the statements are executed in the right order:
"first"
"second"
But the test fails, because the spy method never gets called. Why is that and how can I fix it? I very much appreciate your help.

AngularJS: Testing with FabricJS/Canvas

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.

wait for angular app to be fully rendered from phantom script

I'm writing a script which generates png images from every page of my frontend.
I'm using angular for the UI and capturing the pages with phantom.
The view take a while until angular finish rendering it so I have to wait a little before capturing:
var page = require('webpage').create();
page.open('http://localhost:9000/', function () {
window.setTimeout(function () {
page.render('snapshot.png');
phantom.exit();
}, 2000);
});
I wonder if there is a better way to achieve this. I found angular can emit an event when the page is fully rendered:
$scope.$on('$viewContentLoaded', function () {
// do something
});
And found a way for communicate to phantom with onCallback so I could write something like:
$scope.$on('$viewContentLoaded', function () {
window.callPhantom({ hello: 'world' });
});
Then in other place in phantom script:
page.onCallback = function() {
page.render('snapshot.png');
phantom.exit();
};
But I'm lost in how to inject the angular $viewContentLoaded handle from the phantom script.
I don't know if evaluate/evalueateAsyn are the way to go ...
page.evaluateAsync(function () {
$scope.$on('$viewContentLoaded', function () {
window.callPhantom({ hello: 'world' });
});
});
Maybe I could access the right $scope in some way.
Any ideas?
The associated PhantomJS API is onCallback; you can find the API doc on the wiki.
// in angular
$scope.$on('$viewContentLoaded', function () {
window.callPhantom();
});
// in the phantomjs script
var page = require('webpage').create();
page.onCallback = function() {
page.render('snapshot.png');
phantom.exit();
};
page.open('http://localhost:9000/');
You can get access to the $rootScope by accessing the injector; for example, if you're using the ng-app directive, you can find the element with the directive and call .injector().get("$rootScope") on it. However, I'm not sure if the $viewContentLoaded event will already have fired by then.

Resources