Spy doesn't get called using async jasmine and requirejs - angularjs

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.

Related

How to write a JASMINE test case for my method. Have been trying to mock the event listener. Please point to link or provide me with the mocks

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
});
});
});
});

Protractor not passing my tests without the done() method

I am actually finishing the unit tests of my Angular app. I am currently working on E2E tests using Protractor and Jasmine. Unfortunately, I have the following problem:
I did loads of research online such as http://ramonvictor.github.io/protractor/slides/#/1 and I clearly never see the use of the "done" callback to launch tests.
This first test goes on the createUser page and makes sure that the user tab attribute is set to active. It passes ONLY if I use the done method, which I should not use.
'use strict';
var UserCreate = require('./page-objects/userCreate.pageObjects');
describe('on init', function () {
beforeEach(function() {
var rootUrl = browser.baseUrl + '/#/users/create';
browser.driver.get(rootUrl);
});
it('should set the user tab active', function(done) { // DONE callback
UserCreate.tabs.getAttribute('class').then(function(value) {
expect(value).toEqual('active');
done(); // calling callback
});
});
});
If I repeat the same test without using done(), the test passes even if this time, I want it to fail.
'use strict';
var UserCreate = require('./page-objects/userCreate.pageObjects');
describe('on init', function () {
beforeEach(function() {
var rootUrl = browser.baseUrl + '/#/users/create';
browser.driver.get(rootUrl);
});
it('should set the user tab active', function() {
UserCreate.tabs.getAttribute('class').then(function(value) {
expect(value).toEqual('activeWRONG');
});
});
});
It only fails if I use the done callback.
Here is my config file:
/* conf.js */
' use strict';
exports.config = {
rootElement: '#myApp',
directConnect: true,
seleniumAddress: 'http://localhost:4444/wd/hub',
capabilities: {
browserName: 'chrome',
shardTestFiles: true,
maxInstances: 1
},
framework: 'jasmine',
// specs: ['./*.spec.js'],
baseUrl: 'http://localhost:9001',
defaultTimeoutInterval: 0000,
jasmineNodeOpts: {
showColors: true,
},
suites: {
wip: './userCreate.spec.js',
all: './*spec.js'
},
onPrepare: function() {
browser.driver.get('http://localhost:9001/#/');
element(by.id('ld-link-login')).click();
browser.sleep(500);
element(by.model('username')).sendKeys('test');
element(by.model('password')).sendKeys('test');
element(by.id('nv-login-submit')).click();
return browser.driver.wait(function() {
return browser.driver.getCurrentUrl().then(function(url) {
return /dashboard/.test(url);
});
}, 10000);
}
};
I am having asynchronous problems in deeper tests with the use of done everywhere, so I want to fix this before continuing my tests.
Thank you for your help.
Edit:
Protractor version: ./node_modules/.bin/protractor --version gives Version 3.2.2
userCreate.pageObjects :
'use strict';
module.exports = {
tabs: element(by.id('cc-tab-user'))
};
That's the expected behaviour. If you don't ask for the done() function, jasmine will consider your test as synchronous and finish without waiting for the promise to be resolved.
When you ask for it, your test became asynchronous, and will fail if done() hasn't been called before the timeout ( 5 seconds by default)
See: http://jasmine.github.io/2.0/introduction.html#section-Asynchronous_Support for more info.
You have a problem in the way you are defining the Page Object leading into the problem with the promise resolution order. Follow the style guide and change it to:
var UserCreatePage = function() {
this.tabs = element(by.id('cc-tab-user'));
};
module.exports = UserCreatePage;
Usage:
'use strict';
var UserCreatePage = require('./page-objects/userCreate.pageObjects');
describe('on init', function () {
var userCreatePage;
beforeEach(function() {
var rootUrl = browser.baseUrl + '/#/users/create';
browser.driver.get(rootUrl);
userCreatePage = new UserCreatePage();
});
it('should set the user tab active', function() {
userCreatePage.tabs.getAttribute('class').then(function(value) {
expect(value).toEqual('activeWRONG');
});
});
});
This behavior can happen if your test creates a parallel TaskQueue in your test's Control Flow. For what done() is for and how to use it ( protractor, jasmine) I wrote a few examples of tests giving inconsistent results because of this.
To figure out if this is your problem, you can print out the control flow at various points in your test:
console.log(protractor.promise.controlFlow().getSchedule(false));
Your issue is, that you use the then() to pass value towards your expect-statement.
then() creates a new async-task, which is dropped as subtask. As result of this, your it first finishes with result = success and then your expect-statement gets executed.
It's documented in the Promise/ControlFlow documentation of Selenium.
As expect() already unwraps/resolves a promise, there is no need for then() in your case.
With your done it works, because you take control of the execution and prevent it to continue/finish before your then() is finished.
Try this:
'use strict';
var UserCreate = require('./page-objects/userCreate.pageObjects');
describe('on init', function () {
beforeEach(function() {
var rootUrl = browser.baseUrl + '/#/users/create';
browser.driver.get(rootUrl);
});
it('should set the user tab active', function() {
expect(UserCreate.tabs.getAttribute('class')).toEqual('activeWRONG');
});
});
It is because of the control flow which is not running properly in your code . The control flow is based on the concept of tasks and task queues. Tasks are functions that define the basic unit of work for the control flow to execute. Each task is scheduled via ControlFlow#execute(), which will return a ManagedPromise that will be resolved with the task's result.
The following should work.
let UserCreate = require('./pageobject.page.js');
describe('should navigate to url', function () {
beforeEach(function() {
url = `${browser.baseUrl}/#/users/create`;
browser.driver.get(url);
});
it('should set the user tab active', function() {
expect(UserCreate.tabs.getAttribute('class')).toEqual('activeWRONG');
});
});

AngularJS, Mocha, Chai: testing service with promises

I've found several posts that shows this code as a way to do async unit testing:
The service:
angular.module('miservices', [])
.service('myAppServices', ['$http', 'httpcalls', function($http, httpcalls) {
this.getAccountType = function(){
return httpcalls.doGet('http://localhost:3010/...').then(function(data){
return data;
}, function(error){
...
});
};
...
The test:
describe('testing myAppServices', function(){
beforeEach(module('smsApp'));
it('should handle names correctly', inject(function(myAppServices){
myAppServices.getAccountType()
.then(function(data) {
expect(data).equal({...});
});
...
We're using AngularJS, Mocha, Chai and we have Sinon installed.
The test never gets to the .then part, but why?
Thanks!
If you are testing your service I would recommend to mock your "httpcalls" service (as that is outside scope in this test).
To mock it you can have several ways, one approach would be to have a mocks module that you use only with your unit tests.
angular.module('miservices.mocks', [])
.service('httpcalls', ['$q', function($q) {
this.returnGet = '';
this.doGet = function(url) {
return $q.when(this.returnGet);
};
};
And your unit test then would be something like:
describe('testing myAppServices', function(){
beforeEach(function() {
module('smsApp');
module('miservices.mocks');
});
it('should handle names correctly', inject(function(myAppServices, httpcalls){
httpcalls.returnGet = 'return data';
myAppServices.getAccountType()
.then(function(data) {
expect(data).equal('return data');
});
...
Because we are inserting the mocks module after application module, httpcalls service gets overwritten by its mock version and allows us to test properly myAppServices without further dependencies.

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.

AngularJS $timeout function not executing in my Jasmine specs

I'm trying to test my AngularJS controller with Jasmine, using Karma. But a $timeout which works well in real-life, crashes my tests.
Controller:
var Ctrl = function($scope, $timeout) {
$scope.doStuff = function() {
$timeout(function() {
$scope.stuffDone = true;
}, 250);
};
};
Jasmine it block (where $scope and controller have been properly initialized):
it('should do stuff', function() {
runs(function() {
$scope.doStuff();
});
waitsFor(function() {
return $scope.stuffDone;
}, 'Stuff should be done', 750);
runs(function() {
expect($scope.stuffDone).toBeTruthy();
});
});
When I run my app in browser, $timeout function will be executed and $scope.stuffDone will be true. But in my tests, $timeout does nothing, the function is never executed and Jasmine reports error after timing out 750 ms. What could possibly be wrong here?
According to the Angular JS documentation for $timeout, you can use $timeout.flush() to synchronously flush the queue of deferred functions.
Try updating your test to this:
it('should do stuff', function() {
expect($scope.stuffDone).toBeFalsy();
$scope.doStuff();
expect($scope.stuffDone).toBeFalsy();
$timeout.flush();
expect($scope.stuffDone).toBeTruthy();
});
Here is a plunker showing both your original test failing and the new test passing.
As noted in one of the comments, Jasmine setTimeout mock is not being used because angular's JS mock $timeout service is used instead. Personally, I'd rather use Jasmine's because its mocking method lets me test the length of the timeout. You can effectively circumvent it with a simple provider in your unit test:
module(function($provide) {
$provide.constant('$timeout', setTimeout);
});
Note: if you go this route, be sure to call $scope.apply() after jasmine.Clock.tick.
As $timeout is just a wrapper for window.setTimeout, you can use jasmines Clock.useMock() which mocks the window.setTimeout
beforeEach(function() {
jasmine.Clock.useMock();
});
it('should do stuff', function() {
$scope.doStuff();
jasmine.Clock.tick(251);
expect($scope.stuffDone).toBeTruthy();
});

Resources