Stub a Promise in PhantomJS/Sinon - javascript

I have a class in jquery that calls a service and returns a promise, which is then executed in my main area via a .done and Im trying to wrap that call in a another class I have that will make sure multiple calls are not made for the same ID. However I am finding this very very difficult to test as I can not accurcately get the promise working in phantomJS/Sinon. Heres what the area Im trying to test is
LOCKER.execute(diagRunId, function (unlock) {
SERVICE_ACCESSOR.makeCall params)
.done(function (data) {
console.log("Success!");
unlock();
})
.fail(function (jqXHR, textStatus, errorThrown) {
console.log("Failed!");
unlock();
});
});
and In my test file I have my setup like so
var setup = function() {
P.mock('service-accessor', function () {
return {
makeCall: sinon.stub().returns({})
};
});
P.mock('locker', function () {
var methods = {
execute: function (lockId, wrapped) {
console.log('locked - ' + lockId)
wrapped()
},
unlock: sinon.stub()
};
return {
execute: methods.execute,
unlock: methods.unlock
};
});
P.start();
}
with finally the test just calling the method
aui.suite('Locker Test', function () {
aui.test('should lock and then unlock', testFile, {
setup: setup,
browser: function () {
P.when('utils', 'service-accessor','locker').execute(
'test', function (SERVICE_ACCESSOR, LOCKER) {
UTILS.makeCall("Identifier")
expect(LOCKER.unlock).to.have.been.called.once;
done();
}
);
},
validate: function () {},
});
});
The locker works and begins execution of the service call, but then the service call fails with
Error: PhantomJS: `TypeError: 'undefined' is not a function (evaluating 'SERVICE_ACCESSOR.callService( params).done')` near
L2492> }).fail(function (jqXHR, textStatus, errorThrown) {
From my understanding my mock should return a just a empty object when its being called, but then I dont understand a) Why its going to fail and b) Whats undefined? My assumption is that its because Im not returning three objects, but Im trying to get it to succeed first! How can I correctly stub/mock this?

In the end I didn't make a promise or use a stub. I used the following function that would call the done and fail in my call instead.
function() {
return { done: function(callback) {
if(window.makeParamountCallSuccess) {
callback({"data": "data"});
return {
fail: function(){}
}
} else {
return {
fail: function(failCallback){ failCallback("jqXHR", "textStatus", "errorThrown")}
}
}
}
}
}```

Related

Testing a function that is called from an jQuery AJAX callback with Jasmine

I have a function that makes an AJAX call to a service. I'm attempting to expect that the displayError function is called on a failure.
I have my function ajaxCall that accepts a url. Upon success I pass the result to displaySuccess and when there's an error I pass the details to displayError.
function ajaxCall(url) {
$.ajax({
method: "GET",
url: url,
data: "json",
beforeSend: function (xhr) {
//Do Stuff
},
error: function(xhr, textStatus, errorThrown) { displayError(xhr, textStatus, errorThrow, url)},
success: function (results) { displaySuccess(result) }
});
}
function displayError(xhr, textStatus, errorThrow, url) {
//Do Stuff//
}
function displaySuccess(results) {
//Do Stuff//
}
In Jasmine I have it successfully verifying the URL. My problem is in testing to insure that the displayError and displaySuccess functions are called.
I have the following for this specific issue so far.
describe('The ajaxCall component', function() {
it('should call the error function when the ajax call fails', function () {
var obj = {};
spyOn(obj, 'displayError');
spyOn($, "ajax").and.callFake(function (options) {
options.error();
});
ajaxCall('/myResource/get');
expect(obj.method).toHaveBeenCalled();
});
}
I'm a little new to unit testing and I've searched trying to find suggestions that would help but they make the unit test fail. Where am I going wrong with this?
This all boils down to how you spy on your objects and writing code that is more testable. Let's work through a few strategies.
Strategy 1
Given your current code is not within an object, you could test that these functions are called by simply testing their implementation directly.
Instead of testing that the functions were called, you would test their implementation directly.
Example
describe("strategy 1", function () {
var ajaxSpy;
beforeEach(function () {
ajaxSpy = spyOn($, 'ajax');
ajaxCall();
});
describe("error callback", function () {
beforeEach(function() {
spyOn(window, 'alert');
var settings = ajaxSpy.calls.mostRecent().args[0];
settings.error();
});
describe("when there is an error", function() {
it("should alert an error message", function() {
expect(window.alert).toHaveBeenCalledWith('Error');
});
});
});
});
Strategy 2
While the above works, it can be cumbersome to write tests. Ideally, you want to test the invocation and implementation separately.
To do so, we can spy on these functions. Since these are in the global namespace, you can spy on them through the window object.
Example
describe("strategy 2", function () {
var ajaxSpy;
beforeEach(function () {
ajaxSpy = spyOn($, 'ajax');
ajaxCall();
});
describe("error callback", function () {
beforeEach(function() {
spyOn(window, 'displayError');
var settings = ajaxSpy.calls.mostRecent().args[0];
settings.error();
});
describe("when there is an error", function() {
it("should alert an error message", function() {
expect(window.displayError).toHaveBeenCalled();
});
});
});
});
Strategy 3 (Recommended)
The final strategy, and what I recommend, has a similar setup to the second strategy, except we encapsulate our implementation into a custom object.
Doing so makes the code more testable by wrapping functionality in objects and avoids the global namespace (i.e. window).
Example
describe("solution 3", function() {
var ajaxSpy;
beforeEach(function() {
ajaxSpy = spyOn($, 'ajax');
ajaxService.ajaxCall();
});
describe("error callback", function() {
beforeEach(function() {
spyOn(ajaxService, 'displayError');
var settings = ajaxSpy.calls.mostRecent().args[0];
settings.error();
});
it("should alert an error message", function() {
expect(ajaxService.displayError).toHaveBeenCalled();
});
});
});

Jasmine, spyOn, getJSON and fail

I am trying to learn the Jasmine spyOn function. I get TypeError: Cannot read property 'fail' when using spyOn to test a function that calls jQuery.getJSON.
Here is the function I want to test:
getJsonServerNine: function () {
'use strict';
$.getJSON(queryServer.url, function(simpleJson) {
parseResponse(simpleJson);
}).fail(function(error) {
console.log(error.statusText);
});
}
Here is my test:
it("tests getJSON call", function() {
spyOn($, 'getJSON').and.callFake(function (url, success) {
success({
"nine": 9
});
});
queryServer.getJsonServerNine();
expect(queryServer.queryResult).toBe(9);
});
If I remove the fail callback from getJsonServerNine() then my code works. If I call jquery.ajax() instead of getJSON then I can find a way to get it to work. How can can I get it to work with a call to $.getJSON?
I have seen this answer to a similar question and it is very nice but does not closely match my case, does not work for me, and uses outdated Jasmine syntax.
The object returned by $.getJSON should have a fail() method. In your implementation it returns undefined.
The fake function should return an object like so:
// Source
var queryServer = {
getJsonServerNine: function () {
'use strict';
$.getJSON(queryServer.url, function (simpleJson) {
// parseResponse(simpleJson);
console.log(simpleJson)
queryServer.queryResult = simpleJson[Object.keys(simpleJson)[0]];
}).fail(function (error) {
console.log(error.statusText);
});
}
}
// Test
describe('foo', function () {
it("tests getJSON call", function () {
spyOn($, 'getJSON').and.callFake(function (url, success) {
success({
"nine": 9
});
return {
fail: function() {}
}
});
queryServer.getJsonServerNine();
expect(queryServer.queryResult).toBe(9);
});
});
See JS fiddle here - http://jsfiddle.net/eitanp461/32e17uje/

angularJS controllers functions callback

I am wondering how callbacks works in angularJS.
I have this code working perfectly like this
$scope.loginFB = function () {
hello(FACEBOOK).login(function () {
hello(FACEBOOK).api('me').then(function (profile) {
console.log('successful api call');
dbService.handle_credentials(profile);
$rootScope.$apply(function () {
$location.path('/homePage');
});
}, function(){
console.error('something went wrong with authentification');
});
});
};
but works in weird way when refactored like this
$scope.loginHandler =function () {
hello(FACEBOOK).api('me').then(function (profile) {
console.log('successful api call');
dbService.handle_credentials(profile);
$rootScope.$apply(function () {
$location.path('/homePage');
});
}, function(){
console.error('something went wrong with authentification');
});
};
$scope.loginFB = function () {
hello(FACEBOOK).login($scope.loginHandler());
};
please tell me what i am doing wrong with this refactoring.
By including the params, you are immediately invoking your function callback rather than passing a function reference, which is what you really want to do.
$scope.loginFB = function () {
hello(FACEBOOK).login($scope.loginHandler);
};
If you want to pass a parameter to your callback function, you can use one of two approaches.
Wrap your callback in an anonymous function
$scope.loginFB = function () {
hello(FACEBOOK).login(function() { return $scope.loginHandler(param); });
};
In a modern browser, use .bind().
$scope.loginFB = function () {
hello(FACEBOOK).login($scope.loginHandler.bind(this, param)));
};

How to test and get full code coverage on ajax request and it's done and fail callbacks of jQuery ajax request using Jasmine and Blanket.js?

So I have something like this:
var Utils = {};
Utils.genericAddRowPost = function(url) {
return $.post(url);
};
Utils.genericAddRow = function(dataSource, url) {
genericAddRowPost(url).done(function(data, textStatus, jqXHR) {
// add on to dataSource and other stuff
}).fail(function (jqXHR, textStatus, errorThrown) {
//handle error
});
};
I am attempting to test and achieve 100% code coverage using jasmine and blanket, but I can't seem to be able to mock/execute the done and fail handlers. Any help would be greatly appreciated. I would prefer not to have to restructure any of the code posted if possible.
Using Jasmine, you should be able to spy on your ajax calls and simulate success and failure conditions.
Something like this:
describe("my tests", function () {
beforeEach(function () {
spyOn(jQuery, "ajax");
});
it("should handle success", function () {
genericAddRow(a, b);
// call the success callback
$.ajax.mostRecentCall.args[1].success(data, textStatus, jqXHR);
// do your tests here
});
it("should handle failure", function () {
genericAddRow(a, b);
// call the success callback
$.ajax.mostRecentCall.args[1].error(jqXHR, textStatus, errorThrown);
// do your tests here
});
});
So here is what I did:
it('Verify Utils.genericAddRow', function () {
var wasSuccessful = false;
mockObj = {
data: ko.observableArray([{}])
};
// spy on genericAddRowPost that is called inside this test function
spyOn(Utils, "genericAddRowPost").andReturn({
done: function (callback) {
callback({});
wasSuccessful = true;
return this;
},
fail: function (callback) {
return this;
}
});
// Call our test function and make first set of expectations
Utils.genericAddRow(mockObj, 'fakeUrl');
expect(Utils.genericAddRowPost).toHaveBeenCalledWith('fakeUrl');
expect(wasSuccessful).toBeTruthy();
// Override original spy implementations
Utils.genericAddRowPost().done = function (callback) {
return this;
};
Utils.genericAddRowPost().fail = function(callback) {
callback(null, null, 'testError');
wasSuccessful = false;
return this;
};
// Call our test function and make second set of expectations
Utils.genericAddRow(mockObj, 'anotherFakeUrl');
expect(Utils.genericAddRowPost).toHaveBeenCalledWith('anotherFakeUrl');
expect(wasSuccessful).toBeFalsy();
});
I will edit my question to reflect that genericAddRow and genericAddRowPost are both functions that live on a Utils object literal.

How to test the done and fail Deferred Object by using jasmine

Here is the code about the javascript submit request (1).
Here is the test about mocking the ajax request by using jasmine (2).
I would like to mock the server behaviour. Any ideas?
See the comment in (1) and (2) for more details.
P.S.:
Actually in both case the done and the fail Deferred Object of fakeFunction are called.
(1)
submitForm: function () {
// the server execute fail only if message.val() is empty
// and I would like to mock this behaviour in (2)
backendController.submitForm(message.val()).done(this.onSuccess).fail(this.onError);
},
backendController.submitForm = function (message) {
return $.ajax({
url: 'some url',
type: 'POST',
dataType: 'json',
data: {
message: message
}
}).done(function () {
//some code;
});
};
(2)
describe('When Submit button handler fired', function () {
var submitFormSpy,
fakeFunction = function () {
this.done = function () {
return this;
};
this.fail = function () {
return this;
};
return this;
};
beforeEach(function () {
submitFormSpy = spyOn(backendController, 'submitForm').andCallFake(fakeFunction);
});
describe('if the message is empty', function () {
beforeEach(function () {
this.view.$el.find('#message').text('');
this.view.$el.find('form').submit();
});
it('backendController.submitForm and fail Deferred Object should be called', function () {
expect(submitFormSpy).toHaveBeenCalled();
// how should I test that fail Deferred Object is called?
});
});
describe('if the message is not empty', function () {
beforeEach(function () {
this.view.$el.find('#message').text('some text');
this.view.$el.find('form').submit();
});
it('backendController.submitForm should be called and the fail Deferred Object should be not called', function () {
expect(submitFormSpy).toHaveBeenCalled();
// how should I test that fail Deferred Object is not called?
});
});
});
We actually ran into the same problem, trying to test Deferred objects that represent AJAXed template scripts for on-the-fly templating. Our testing solution involves using the Jasmine-Ajax library in conjunction with Jasmine itself.
So probably it will be something like this:
describe('When Submit button handler fired', function () {
jasmine.Ajax.useMock();
describe('if the message is empty', function () {
beforeEach(function() {
spyOn(backendController, 'submitForm').andCallThrough();
// replace with wherever your callbacks are defined
spyOn(this, 'onSuccess');
spyOn(this, 'onFailure');
this.view.$el.find('#message').text('');
this.view.$el.find('form').submit();
});
it('backendController.submitForm and fail Deferred Object should be called', function () {
expect(backendController.submitForm).toHaveBeenCalledWith('');
mostRecentAjaxRequest().response({
status: 500, // or whatever response code you want
responseText: ''
});
expect( this.onSuccess ).not.toHaveBeenCalled();
expect( this.onFailure ).toHaveBeenCalled();
});
});
Another thing, if you can, try to break up the functionality so you're not testing the entire DOM-to-response-callback path in one test. If you're granular enough, you can actually test asynchronous Deferred resolutions by using Deferred objects themselves inside your tests!
The key is to actually use Deferred objects within your tests themselves, so that the scope of the expect call is still within your it function block.
describe('loadTemplate', function() {
it('passes back the response text', function() {
jasmine.Ajax.mock();
loadTemplate('template-request').done(function(response) {
expect(response).toBe('foobar');
});
mostRecentAjaxRequest().response({ status:200, responseText:'foobar' });
});
});
Here is how I managed to do it.
Essentially, the $.ajax object returns a Deferred object, so you can spy on $.ajax and return a Deferred and then trigger it manually to run the .done() code in your JavaScript
Code
Index.prototype.sendPositions = function() {
var me = this;
$.ajax({
...
}).done(function(data, textStatus, jqXHR) {
me.reload();
}).fail(function(jqXHR, textStatus, errorThrown) {
console.log(errorThrown);
});
};
Test
it("should reload the page after a successful ajax call", function(){
var deferred = new jQuery.Deferred();
spyOn($, 'ajax').andReturn(deferred);
spyOn(indexPage, 'reload');
indexPage.sendPositions();
deferred.resolve('test');
expect(indexPage.reload).toHaveBeenCalled();
});
It would be much easier to test if you had a var with the ajax request promise object. In that case you could do:
it('should do an async thing', function() {
var mutex = 1;
var promF = jasmine.createSpy('prF');
runs( function() {
var promise1 = $.ajax();
promise1.always(function(){
mutex--;
});
promise1.fail(function(){
promF();
});
});
waitsFor(function(){
return !mutex;
}, 'Fetch should end', 10000);
runs( function() {
expect(promF).toHaveBeenCalled();
});
});
Below I post untested code that might suit you. I suppose that the ajax call is initialized from the .submit() class? Maybe you should initialize the ajax request from a runs() block and not from beforeEach(), but you should try which one works.
describe('When Submit button handler fired and city is defined', function () {
var ajaxRequestSpy,
failSpy, successSpy, alwaysSpy,
mutex;
beforeEach(function () {
ajaxRequestSpy = spyOn(backendController, 'ajaxRequest').andCallThrough();
failSpy = spyOn(ajaxRequestSpy(), 'fail').andCallThrough()
successSpy = spyOn(ajaxRequestSpy(), 'success').andCallThrough();
mutex = 1; // num of expected ajax queries
alwaysSpy = spyOn(ajaxRequestSpy(), 'always').andCallFake(function() {
mutex--;
});
this.view = new MyView({
el: $('<div><form>' +
'<input type="submit" value="Submit" />' +
'<input type="text" name="city">' +
'</form></div>')
});
this.view.$el.find('form').submit();
});
it('backendController.ajaxRequest should be called', function () {
runs( function() {
// maybe init ajax here ?
});
waitsFor( function() {
return !mutex;
}, 'ajax request should happen', 5000);
runs( function() {
expect(ajaxRequestSpy).toHaveBeenCalled(); // true
expect(failSpy).toHaveBeenCalled(); // Error: Expected spy fail
// to have been called.
});
});
});
But, I am not sure that the line
failSpy = spyOn(ajaxRequestSpy(), 'fail').andCallThrough();
does what you want. Is it possible to spy on another spy? And if yes why are you calling the spy ? Maybe you should try
failSpy = spyOn(ajaxRequestSpy, 'fail').andCallThrough();

Categories

Resources