jasmine test function that returns a promise - javascript

I have the following function implementation
function getRepo(url) {
var repos = {};
if (repos.hasOwnProperty(url)) {
return repos[url];
}
return $.get(url)
.then(repoRetrieved)
.fail(failureHandler);
function repoRetrieved(data) {
return repos[url] = data;
}
function failureHandler(err, xhr) {
throw new Error(xhr.responseText);
}
}
And i wrote the following tests:
describe('"getRepo" method', function() {
var getDeffered;
var $;
beforeEach(function() {
getDeffered = Q.defer();
$ = jasmine.createSpyObj('$', ['get']);
$.get.and.returnValue(getDeffered.promise);
});
it('should return a promise', function(){
expect(getRepo('someURL')).toEqual(getDeffered.promise);
});
});
And this test fails. I think because i call the then method.
It does not fail if function implementation is:
function getRepo(url) {
return $.get(url);
}
this is the message jasmine throws when using Q.defer()
Expected { promiseDispatch : Function, valueOf : Function, inspect : Function }
to equal { promiseDispatch : Function, valueOf : Function, inspect : Function }.
And this is the message if i use jQuery Deferred:
Expected { state : Function, always : Function, then : Function, promise : Function, pipe : Function, done : Function, fail : Function, progress : Function }
to equal { state : Function, always : Function, then : Function, promise : Function, pipe : Function, done : Function, fail : Function, progress : Function }.
jQuery Deferred test implementation:
describe('"getRepo" method', function() {
var getDeffered;
var $;
beforeEach(function() {
getDeffered = real$.Deferred();
$ = jasmine.createSpyObj('$', ['get']);
$.get.and.returnValue(getDeffered.promise());
});
it('should return a promise', function(){
expect(getRepo('someURL')).toEqual(getDeffered.promise());
});
});

Instead of testing the returned object being a promise, you can test the resolved value directly, which gives a more accurate result:
describe('"getRepo" method', function() {
var originalGet;
beforeAll(function() {
originalGet = $.get;
});
beforeEach(function() {
$.get = originalGet; // Restore original function
});
it('should update the data[] array when resolved', function(done) {
// This is an asynchronous test, so we need to pass done ^^^^
var expectedResult = 'test data';
$.get = function() {
var dfd = $.Deferred();
dfd.resolve(expectedResult);
return dfd.promise();
};
getRepo('someURL').then(function(repos) {
var actualResult = repos['someUrl'];
expect(actualResult).toEqual(expectedResult);
done(); // This is an asynchronous test. We must mark the test as done.
});
});
});
Please note that jQuery's Promise implementation is pretty bad. It's best if you use native Promises, or a library like Bluebird.

You are creating a local $ variable, then assigning a spy object to it. Which is not the same $ that getRepo uses.
You should remove the local variable var $; and mock the original $.get.
Try this:
describe('"getRepo" method', function() {
var testPromise;
beforeEach(function() {
testPromise = real$.Deferred().promise();
spyOn($, 'get').andCallFake(function() {
return testPromise;
});
});
it('should return a promise', function(){
expect(getRepo('someURL')).toEqual(testPromise);
});
});

Related

How to efficiently test deferred always

When writing tests for code in a jQuery ajax (or get) always part or even in bluebird promises finally like this:
function doStuff() {
console.log('stuff done');
}
function someFunction() {
return $.get('someurl').always(doStuff);
}
I always find myself writing (QUnit) tests for this like:
QUnit.test("doStuff will be called when someFunction succeeds", function (assert) {
var deferred = $.Deferred();
var backup = $.get;
$.get = function () { return deferred; };
var doStuffIsCalled = false;
doStuff = function(){ doStuffIsCalled = true; };
deferred.resolve({});
return someFunction().then(function(){
$.get = backup;
assert.ok(doStuffIsCalled);
});
});
QUnit.test("doStuff will be called when someFunction fails", function (assert) {
var deferred = $.Deferred();
var backup = $.get;
$.get = function () { return deferred; };
var doStuffIsCalled = false;
doStuff = function(){ doStuffIsCalled = true; };
deferred.reject(new Error('some error'));
return someFunction().catch(function(){
$.get = backup;
assert.ok(doStuffIsCalled);
});
});
This works, but is somewhat verbose. Is there some more efficient way, preferrably in a single test, to directly test code called in the always part of a deferred?
You can use Sinon.js to mock jQuery ajax (or get) as well as promises in general.
One approach could be:
function someFunction() {
return $.get('/mytest').always(doStuff);
}
function givenFncExecutesAndServerRespondsWith(reponseNumber, contentType, response) {
server.respondWith("GET", "/mytest", [reponseNumber, contentType, response]);
someFunction();
server.respond();
}
module("Testing server responses", {
setup: function () {
server = sinon.sandbox.useFakeServer();
doStuff = sinon.spy();
},
teardown: function () {
server.restore();
}
});
test("doStuff will be called when someFunction succeeds", function () {
givenFncExecutesAndServerRespondsWith(200, '', '');
ok(doStuff.called, "spy called once");
});
test("doStuff will be called when someFunction fails", function () {
givenFncExecutesAndServerRespondsWith(500, '', '');
ok(doStuff.called, "spy called once");
});
You can play with this code in this fiddle. If instead of always you used done or fail for calling the callback, the corresponding test would fail.
The explanation to the code would be as follows:
Create a fake server and a spy that will act as the always callback.
Modify the response number of the server's response according to what we're testing.
Hope it helps.

Unit Testing with Promises and Spies

I have the following files:-
target.js
var target = function(repository, logger) {
return {
addTarget : function(target) {
repository.add(target).then(
function (newTarget) {
console.log("added");
logger.info("added");
},
function (err) {
console.log("error");
logger.info("error");
}
);
}
};
};
module.exports = target;
targetTest.js
var chai = require("chai"),
expect = chai.expect,
sinon = require("sinon"),
Promise = require("bluebird"),
baseTarget = require("../target");
describe("target", function(){
it("should log error when it occurs", function() {
var mockRepository = {
add : sinon.stub().returns(Promise.reject(new Error()))
};
var mockLogger = {
info : sinon.spy()
};
var target = baseTarget(mockRepository, mockLogger);
target.addTarget("target");
expect(mockLogger.info.calledWith("error")).to.be.true;
});
});
The issue I have is that expect(mockLogger.info.calledWith("error")).to.be.true; returns false because add method on the repository is async and so hasn't executed yet. Is there a pattern for doing this properly.
This is really more of a question about 'how Promises work' than how they work within test frameworks - the answer to which is that their behaviour remains exactly the same.
Is there a pattern for doing this properly.
It is not so much a pattern as it is what Promises are built to do. Each success handler of a then is executed in sequence on success of the last. In your code we can return the Promise created by calling repository#add as you would if you wanted to use its result or perform some external dependent operation outside of addTarget:
addTarget: function (target) {
return repository
// ^^^^^^
.add(target)
.then(function (newTarget) {
console.log("added");
logger.info("added");
}, function (err) {
console.log("error");
logger.info("error");
});
}
Then place your expectation inside a then that will be executed on success of all members of the Promise chain created in addTarget:
target.addTarget("target").then(function () {
expect(mockLogger.info.calledWith("error")).to.be.true;
cb();
});
Asynchronous Tests
You will notice in the example above that there is also a call to a function cb. Due to your test being asynchronous you need to 'tell' the test framework when the test has completed. This is most often done by declaring your it function with a parameter, from which the framework will infer that the test is asynchronous and pass in a callback:
describe("target", function () {
it("should log error when it occurs", function (cb) {
// ^^^^
});
});

using promise with angular.js

i use angular.js in front side.
in my controller.js i defined an init() method that will be called in
init of my controller.
Init method definition:
var init = function () {
$scope.callTeamsService();
if ($scope.teams.length == 0){
....
}else{
...
}
.....
};
in $scope.callTeamsService i filled in $scope.teams variable.
$scope.callTeamsService method definition:
$scope.callTeamsService = function(){
NavService.getTeams(function (response) {
$timeout(function () {
$scope.teams = response;
}
}, 200);
});
};
In my service.js i defined a getTeams method as follow:
service.getEquipes = function (callback) {
$http.get(urlBase+'users/' + $rootScope.globals.currentUser.loggedUser.idUser + '/teams')
.success(function (response) {
callback(response);
});
};
My problem is when $scope.teams.length == 0 condition is reached the
service.getEquipes method in my service.js is not yet called.
How can i modify this code in order to finish the execution of $scope.callTeamsService method before reaching $scope.teams.length == 0 condition.
service/factory
service.getEquipes = function () {
return $http.get(urlBase+'users/' + $rootScope.globals.currentUser.loggedUser.idUser + '/teams');
};
// controller
var promise = NavService.getTeams.then (
function(data) {
//assign to $scope or do logic
},
function(err){
console.log(err)
}
)
How can i modify this code in order to finish the execution of $scope.callTeamsService method before reaching $scope.teams.length == 0 condition.
That's the wrong way round - you need to wait with executing the $scope.teams.length == 0 condition until the $scope.callTeamsService method has finished.
The classical method would be to give the $scope.callTeamsService method a callback parameter and call that in the timeout instead of $scope.teams = response;. Then you can put your condition in the init function in the callback that you pass.
However, you seem to want to use promises. For that, all of your functions (that all are asynchronous) should return a promise:
service.getEquipes = function (callback) {
return $http.get(urlBase+'users/' + $rootScope.globals.currentUser.loggedUser.idUser + '/teams');
}
(that was easy, $http already returns promises)
$scope.callTeamsService = function() {
return NavService.getTeams().then(function(teams) {
return $timeout(function() {
return teams;
}, 200);
});
};
(and $timeout does as well - by invoking then and returning it from the callback you can chain them and get a new promise for both)
function init() {
return $scope.callTeamsService().then(function(teams) {
$scope.teams = teams;
if (teams.length == 0) {
…
} else {
…
}
});
}

Multiple doneCallbacks in Deferred then

The documentation for deferred.then() states that doneCallbacks is A function, or array of functions, called when the Deferred is resolved.
When I write either .then(new Array(getData2, showDiv)) or .then([getData2, showDiv])
none of them are called.
What is the correct syntax?
Update
Should the syntax for array be .then(new Array(getData2(), showDiv())) or .then([getData2(), showDiv()]) with parenthesis?
See http://jsfiddle.net/JSw5y/894/
This seems to be might be a bug in jQuery.
A simple workaround;
var CallbackHandler = (function () {
var callbacks = [];
return {
'add': function (fn) {
callbacks.push(fn);
return this;
},
'executor': function () {
var calledBy = this;
$.each(callbacks, function () {
this.call(calledBy);
});
}
};
})();
CallbackHandler
.add(function () {
// first callback
})
.add(function () {
// second callback
});
// Called as:
$.when({a: 1})
.then(CallbackHandler.executor);

Chaining ajax requests with jQuery's deferred

I have a web app which must call the server multiple times. So far, I had a long nested callback chain; but I would like to use jQuery's when,then etc. functionality. However, I can't seem to get stuff running again after using a then.
$
.when ($.get('pages/run-tool.html'))
.then (function (args)
{
// This works fine
alert(args);
$('#content').replaceWith (args);
$('#progress-bar').progressbar ({value: 0});
})
.then ($.get('pages/test.html'))
.done (function(args)
{
// This prints the same as the last call
alert (args);
});
What am I doing wrong? I guess its some scoping issue, as I can see the second get call being executed. Using two different args variables does not help as the argument passed to the done function is still the first get request.
As an update:
With modern jquery (1.8+) you don't need the preliminary when because get returns a Deferred Promise.
Also, pipe is deprecated. Use then instead. Just be sure to return the result of the new get which becomes the Promise attached to by subsequent then/*done*/fail calls.
So:
$.get('pages/run-tool.html')
.then (function (args) { // this will run if the above .get succeeds
// This works fine
alert(args);
$('#content').replaceWith (args);
$('#progress-bar').progressbar ({value: 0});
})
.then (function() { // this will run after the above then-handler (assuming it ran)
return $.get('pages/test.html'); // the return value creates a new Deferred object
})
.done (function(args) { // this will run after the second .get succeeds (assuming it ran)
alert (args);
});
All three callbacks (the two with then and the one with done) are applied to the same request – the original when call. This is because then returns the same Deferred object, rather than a new one, so that you can add multiple event handlers.
You need to use pipe instead.
$
.when ($.get('pages/run-tool.html'))
.then (function (args)
{
// This works fine
alert(args);
$('#content').replaceWith (args);
$('#progress-bar').progressbar ({value: 0});
})
.pipe (function() {
return $.get('pages/test.html'); // the return value creates a new Deferred object
})
.done (function(args)
{
alert (args);
});
Here is an wonderfully simple and highly effective AJAX chaining / queue plugin. It will execute you ajax methods in sequence one after each other.
It works by accepting an array of methods and then executing them in sequence. It wont execute the next method whilst waiting for a response.
//--- THIS PART IS YOUR CODE -----------------------
$(document).ready(function () {
var AjaxQ = [];
AjaxQ[0] = function () { AjaxMethod1(); }
AjaxQ[1] = function () { AjaxMethod2(); }
AjaxQ[3] = function () { AjaxMethod3(); }
//Execute methods in sequence
$(document).sc_ExecuteAjaxQ({ fx: AjaxQ });
});
//--- THIS PART IS THE AJAX PLUGIN -------------------
$.fn.sc_ExecuteAjaxQ = function (options) {
//? Executes a series of AJAX methods in dequence
var options = $.extend({
fx: [] //function1 () { }, function2 () { }, function3 () { }
}, options);
if (options.fx.length > 0) {
var i = 0;
$(this).unbind('ajaxComplete');
$(this).ajaxComplete(function () {
i++;
if (i < options.fx.length && (typeof options.fx[i] == "function")) { options.fx[i](); }
else { $(this).unbind('ajaxComplete'); }
});
//Execute first item in queue
if (typeof options.fx[i] == "function") { options.fx[i](); }
else { $(this).unbind('ajaxComplete'); }
}
}
The answer cdr gave, which has the highest vote at the moment, is not right.
When you have functions a, b, c each returns a $.Deferred() object, and chains the functions like the following:
a().then(b).then(c)
Both b and c will run once the promise returned from a is resolved. Since both then() functions are tied to the promise of a, this works similiar to other Jquery chaining such as:
$('#id').html("<div>hello</div>").css({display:"block"})
where both html() and css() function are called on the object returned from $('#id');
So to make a, b, c run after the promise returned from the previous function is resolved, you need to do this:
a().then(function(){
b().then(c)
});
Here the call of function c is tied to the promise returned from function b.
You can test this using the following code:
function a() {
var promise = $.Deferred();
setTimeout(function() {
promise.resolve();
console.log("a");
}, 1000);
return promise;
}
function b() {
console.log("running b");
var promise = $.Deferred();
setTimeout(function () {
promise.resolve();
console.log("b");
}, 500);
return promise;
}
function c() {
console.log("running c");
var promise = $.Deferred();
setTimeout(function () {
promise.resolve();
console.log("c");
}, 1500);
return promise;
}
a().then(b).then(c);
a().then(function(){
b().then(c)
});
Change the promise in function b() from resolve() to reject() and you will see the difference.
<script type="text/javascript">
var promise1 = function () {
return new
$.Deferred(function (def) {
setTimeout(function () {
console.log("1");
def.resolve();
}, 3000);
}).promise();
};
var promise2 = function () {
return new
$.Deferred(function (def) {
setTimeout(function () {
console.log("2");
def.resolve();
}, 2000);
}).promise();
};
var promise3 = function () {
return new
$.Deferred(function (def) {
setTimeout(function () {
console.log("3");
def.resolve();
}, 1000);
}).promise();
};
var firstCall = function () {
console.log("firstCall");
$.when(promise1())
.then(function () { secondCall(); });
};
var secondCall = function () {
console.log("secondCall")
$.when(promise2()).then(function () { thirdCall(); });
};
var thirdCall = function () {
console.log("thirdCall")
$.when(promise3()).then(function () { console.log("done"); });
};
$(document).ready(function () {
firstCall();
});
</script>
I thought I would leave this little exercise here for anyone who may find it useful, we build an array of requests and when they are completed, we can fire a callback function:
var urls = [{
url: 'url1',
data: 'foo'
}, {
url: 'url2',
data: 'foo'
}, {
url: 'url3',
data: 'foo'
}, {
url: 'url4',
data: 'foo'
}];
var requests = [];
var callback = function (result) {
console.log('done!');
};
var ajaxFunction = function () {
for (var request, i = -1; request = urls[++i];) {
requests.push($.ajax({
url: request.url,
success: function (response) {
console.log('success', response);
}
}));
}
};
// using $.when.apply() we can execute a function when all the requests
// in the array have completed
$.when.apply(new ajaxFunction(), requests).done(function (result) {
callback(result)
});
My way is to apply callback function:
A(function(){
B(function(){
C()})});
where A, B can be written as
function A(callback)
$.ajax{
...
success: function(result){
...
if (callback) callback();
}
}

Categories