Click run a couple of times - these tests alternate between pass and fail.
http://jsfiddle.net/samselikoff/hhk6u/3/
Both tests require companies, but I don't know how to isolate the events. Any ideas?
Answer:
Jeferson is correct. One easy way to solve this, is to use events.once instead of events.on. This way you clean up your events from each test.
You are running synchronous tests while the callbacks of the triggered events are asynchronous.
To fix that you have to implement an "asyncTest" and call the start function when the test assertions are ready to be collected.
Your second test was failing with the message:
Called start() while already started (QUnit.config.semaphore was 0
already)
teste
Exactly because it was a synchronous test, already started and you were calling the start() method again.
And also in your first test, that doesn't specify a callback function, you have to wrap your async call in another function so you can call start() when the simulated AJAX call is ready.
I updated your JSFiddle with working code: http://jsfiddle.net/hhk6u/8/
The new code is:
QUnit.config.autostart = false;
QUnit.config.testTimeOut = 1000;
asyncTest('Some test that needs companies.', function() {
function getCompanies() {
var companies = new Companies();
ok(1);
start();
}
setTimeout(getCompanies, 500);
});
asyncTest('Some other async test that triggers a listener in companies.', function() {
var companies = new Companies();
events.trigger("userSet:company", { name: "Acme", id: 1 });
stop();
events.on('fetched:departments', function(response) {
console.log(response);
deepEqual(response, [1, 2, 3]);
start();
});
});
Note that in the first test method I created a "getCompanies" function that will be called after an interval (500 milliseconds) that should be enough for the AJAX call to finish.
You have to adjust this time according to your needs, and also ajust "testTimeOut" value so your methods won't run indefinitely.
See QUnit config docs for more details: http://api.qunitjs.com/QUnit.config/
Isn't your initial Fiddle potentially failing because you are not creating your event bus at the start of each test (in a setup() method), so your asynchronous event from the first test could be fired when the 2nd test is running and then cause the 2nd test to handle it twice, calling start() twice.
See my updated Fiddle http://jsfiddle.net/e67Zh/ it creates the event bus each time.
You might want to also set a timeout in your qunit tests for scenarios where the event doesn't fire.
/* Backbone code
*******************/
var Company = Backbone.Model.extend({});
var Companies = Backbone.Collection.extend({
initialize: function() {
var self = this;
events.on("userSet:company", function(company) {
self.selectedCompany = company;
// Simulate an AJAX request
setTimeout(function() {
events.trigger("fetched:departments", [1, 2, 3]);
}, 500);
});
},
selectedCompany: ''
});
/* Tests
*******************/
module("test with new event bus each time", {
setup: function() {
events = _.clone(Backbone.Events);
}
});
test('Some test that needs companies.', function() {
var companies = new Companies();
ok(1);
});
test('Some other async test that triggers a listener in companies.', function() {
var companies = new Companies();
events.trigger("userSet:company", { name: "Acme", id: 1 });
stop();
events.on('fetched:departments', function(response) {
console.log(response);
deepEqual(response, [1, 2, 3]);
start();
});
});
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 am really new to angularJS. I need to develop a page where angular JS wait for a event to happen at server side so angular JS should keep checking server using $http call in every 2 seconds. Once that event completes Angular should not invoke any $http call to server again.
I tried different method but it gives me error like "Watchers fired in the last 5 iterations: []"
Please let me know how to do it.
Following is my code
HTML
<div ng-controller="myController">
<div id="divOnTop" ng-show="!isEventDone()">
<div class="render"></div>
</div>
</div>
Angular JS
var ngApp = angular.module("ngApp",[]);
ngApp.controller('myController', function ($scope, $http) {
$scope.ready = false;
$scope.isEventDone = function () {
$scope.ready = $scope.getData();
return $scope.ready;
};
$scope.getData = function () {
if (! $scope.ready) {
$http.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.ready = Boolean(response.data);
});
}
};
setInterval($scope.isPageReady, 5000);
});
A few things here.
I'm not convinced the accepted answer actually works nor solves the initial problem. So, I'll share my 2 cents here.
$scope.ready = $scope.getData(); will set $scope.ready to undefined each time since this method doesn't return anything. Thus, ng-show="!isEventDone()" will always show the DOM.
You should use angular's $interval instead of setInterval for short-polling in angular.
Also, I've refactored some redundancy.
var ngApp = angular.module("ngApp",[]);
ngApp.controller('myController', function ($scope, $http, $interval) {
var intervalPromise = $interval($scope.getData, 5000);
$scope.getData = function () {
if (! $scope.isEventDone) {
$http
.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.isEventDone = Boolean(response.data);
if($scope.isEventDone) {
$interval.cancel(intervalPromise);
}
});
}
else {
$interval.cancel(intervalPromise);
}
};
});
This should work and solve your initial problem. However, there's a scenario where your server may be on a high load and takes 3 seconds to respond. In this case, you're calling the server every 2 seconds because you're waiting for 5 seconds after the previous request has started and not waiting for after the previous request has ended.
A better solution than this is to use a module like async which easily handles asynchronous methods. Combining with $timeout:
var ngApp = angular.module("ngApp",[]);
ngApp.controller('myController', function ($scope, $http, $timeout) {
var getData = function(cb){
if(!$scope.isEventDone) return cb();
$http.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.isEventDone = Boolean(response.data);
cb();
});
};
// do during will run getData at least once
async.doDuring(getData, function test(err, cb) {
// asynchronous test method to see if loop should still occur
// call callback 5 seconds after getData has responded
// instead of counting 5 seconds after getData initiated the request
$timeout(function(){
cb(null, !$scope.isEventDone);
// if second param is true, call `getData()` again otherwise, end the loop
}, 5000);
}, function(err) {
console.log(err);
// if you're here, either error has occurred or
// the loop has ended with `$scope.isEventDone = true`
});
});
This will call the timeout after the request has ended.
A better alternative, if you have control of the server, is to use a websocket which will enable long-polling (server notifies the client instead of client making frequent requests) and this will not increase significant load on the server as clients grow.
I hope this helps
In your example $scope.pageIsReady does not exist. What you could do is inject the $timeout service into your controller and wrap your http call inside of it:
var timeoutInstance = $timeout(function(){
$http.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.ready = Boolean(response.data);
if($scope.ready){
$timeout.cancel(timeoutInstance);
else
$scope.getData();
}
});
},5000);
cancel will stop the timeout from being called. I have not tested this but it should be along those lines.
Also not sure what type of backend you are using but if it is .net you could look into SignalR which uses sockets so the server side tells the front end when it is ready and therefore you no longer need to use polling.
$scope.$on("stopSpinner", function() {
$('body').mask('hide');
});
the above code works that when stopSPinner is event is recieved, it executes. But my requirement is little more involved. I want to wait for two different events to recive before executing a function. Order of events could be different but i want to function to only execute once both events have occured. how can this be achived
Use $q.all and promises, like this:
var deferred1 = $q.defer();
$scope.$on("stopSpinner", function()
{
$('body').mask('hide');
deferred1.resolve();
});
var deferred2 = $q.defer();
$scope.$on("event2", function()
{
$('body').mask('hide');
deferred2.resolve();
});
$q.all([deferred1, deferred2]).then(
function(){
//your code after the 2 events...
})
This way, you'll be able to wait for 2 async task to complete in a single function.
I'm very new in unit testing angularjs applications and I think I don't understand the main concept of testing promise based services on angularjs.
I will directly start with my example:
I have a SQLite db-service which has this method:
var executeQuery = function(db,query,values,logMessage) {
return $cordovaSQLite.execute(db, query, values).then(function(res) {
if(res.rows.length>0) return res;
else return true;
}, function (err) {
return false;
});
};
And I want to write a test case, where I execute a query and then I want to get the return value of the executeQuery function of my service.
My test description is this:
describe("Test DatabaseCreateService‚", function () {
var DatabaseCreateService,cordovaSQLite,ionicPlatform,rootScope,q;
var db=null;
beforeEach(module("starter.services"));
beforeEach(module("ngCordova"));
beforeEach(module("ionic"));
beforeEach(inject(function (_DatabaseCreateService_, $cordovaSQLite,$ionicPlatform,$rootScope,$q) {
DatabaseCreateService = _DatabaseCreateService_;
cordovaSQLite = $cordovaSQLite;
ionicPlatform = $ionicPlatform;
q = $q;
rootScope = $rootScope;
ionicPlatform.ready(function() {
db = window.openDatabase("cgClientDB-Test.db", '1', 'my', 1024 * 1024 * 100);
});
}));
describe("Test DatabaseCreateService:createTableLocalValues", function() {
it("should check that the createTableLocalValues was called correctly and return correct data", function() {
var deferred = q.defer();
deferred.resolve(true);
spyOn(DatabaseCreateService,'createTableLocalValues').and.returnValue(deferred.promise);
var promise = DatabaseCreateService.createTableLocalValues(db);
expect(DatabaseCreateService.createTableLocalValues).toHaveBeenCalled();
expect(DatabaseCreateService.createTableLocalValues).toHaveBeenCalledWith(db);
expect(DatabaseCreateService.createTableLocalValues.calls.count()).toEqual(1);
promise.then(function(resp) {
expect(resp).not.toBe(undefined);
expect(resp).toBe(true);
},function(err) {
expect(err).not.toBe(true);
});
rootScope.$apply();
});
});
});
This test description works but it does not return the value from the function instead of it return what gets resolved in deferred.resolve(true);
What I want to do is the call the DatabaseCreateService.createTableLocalValues function and resolve the data which gets returned from the function.
The createTableLocalValues function is this:
var createTableLocalValues = function(db) {
var query = "CREATE TABLE IF NOT EXISTS `local_values` (" +
"`Key` TEXT PRIMARY KEY NOT NULL," +
"`Value` TEXT );";
return executeQuery(db,query,[],"Create cg_local_values");
};
Well if I run this method on browser or device I get a true back if everything works fine and the table gets created. So how do I get this real true also in the test description and not a fake true like in my example above?
Thanks for any kind of help.
Example 2 (with callThrough):
describe('my fancy thing', function () {
beforeEach(function() {
spyOn(DatabaseCreateService,'createTableSettings').and.callThrough();
});
it('should be extra fancy', function (done) {
var promise = DatabaseCreateService.createTableSettings(db);
rootScope.$digest();
promise.then(function(resp) {
console.log(resp);
expect(resp).toBeDefined();
expect(resp).toBe(true);
done();
},function(err) {
done();
});
});
});
Log message in karma-runner:
LOG: true
Chrome 46.0.2490 (Mac OS X 10.11.1) Test DatabaseCreateService‚ testing createTable functions: my fancy thing should be extra fancy FAILED
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
Chrome 46.0.2490 (Mac OS X 10.11.1): Executed 42 of 42 (1 FAILED) (8.453 secs / 8.03 secs)
UPDATE:
It turned out that this problem has something to do with the $cordovaSQLite.executeQuery function itself. Somehow it have no timeout on the promise and thats what the error causes. I changed the execute function of ng-cordova to this. (hoping that this change does not break anything working)
execute: function (db, query, binding) {
var q = Q.defer();
db.transaction(function (tx) {
tx.executeSql(query, binding, function (tx, result) {
q.resolve(result);
},
function (transaction, error) {
q.reject(error);
});
});
return q.promise.timeout( 5000, "The execute request took too long to respond." );
}
With that change the tests passes correctly!
You can spy on a function, and delegate to the actual implementation, using
spyOn(DatabaseCreateService,'createTableLocalValues').and.callThrough();
You might also need to call rootScope.$digest() after you call your function, so the promise will resolve.
Edit:
When testing async code, you should use the done pattern:
it('should be extra fancy', function (done) {
var promise = DatabaseCreateService.createTableSettings(db);
rootScope.$digest();
promise.then(function(resp) {
console.log(resp);
expect(resp).toBeDefined();
expect(resp).toBe(false);
expect(resp).toBe(true);
done();
});
});
A suggestion on the way you're asserting in your test:
In your test, you are calling then on your returned promise in order to make your assertions:
promise.then(function(resp) {
expect(resp).not.toBe(undefined);
expect(resp).toBe(true);
},function(err) {
expect(err).not.toBe(true);
});
Which is forcing you to add an assertion in an error function so that your test still fails if the promise doesn't resolve at all.
Try using Jasmine Promise Matchers instead. It will make your test code that easier to read and lead to clearer error messages when your tests fail. Your test would look something like this:
expect(promise).toBeResolvedWith(true);
I'm working with backbone and jasmine and now
trying to test the callCount of 'sync' method, when model saved.
For some strange reason the sync handler continue to handle the sync even after the done variable is true (this is there i planned to stop the test)
I'm a newbie for jasmine so i guess i didn't understand something elementar here...
here is my speck:
describe('Model :: User', function() {
var mockData = { name: 'Foo Bar' };
beforeEach(function() {
var that = this,
done = false;
require(['app/namespace','app/models/UserModel','app/collections/UsersCollection'], function(namespace, UserModel ,UsersCollection) {
that.users = new UsersCollection();
that.user = new UserModel();
done = true;
});
waitsFor(function() {
return done;
}, "Create Models");
});
afterEach(function(){
var done = false,
isDone = function(){ return done; };
this.users.fetch({
success: function(c) {
console.log('after the test calling destory of collection...')
c.each(function(m){
m.destroy();
});
done = true;
}
});
waitsFor(isDone);
done = false;
this.user.destroy({
success: function(){
console.log('after the test calling destory of model...')
done = true;
}
});
waitsFor(isDone);
});
describe('.save()', function() {
it('should call sync when saving', function() {
var done = false,
spy = jasmine.createSpy();
this.user.on('sync', spy);
this.user.on('sync', function(){
console.log('checking spy.callCount-'+spy.callCount);
//------------------------------------------------
if(!done)//why i need this if ?!
expect(spy.callCount).toEqual(1);
done = true;
}, this);
this.user.save(mockData);
waitsFor(function() { return done; });
});
});
});
The test workiing correctly only if i add "if(!done)" condition before expect statement,
otherwise it continue to count sync calls that caused by destroy after the test...
Thanks forwards
There are some of issues with this test. First of all, you dont need to test that the sync event is fired when saving your model cause this is provided by another framework, which is hopefully tested.
Second you should use the fake server of SinonJs to not mess with async calls. With sinon your request will be called immediately, which means you dont need waitsFor. Also assertions in callback seems a bit odd.
this.server = sinon.fakeServer.create();
server.respondWith({data: 'someData'})
server.autoRespond = true; //so when the request start the fake server will immediately call the success callback
var spy = jasmine.createSpy();
this.user.on('sync', spy);
this.user.save(mockData);
expect(spy.callCount).toEqual(1);