I really like this resource for AJAX in general and it works in terms of testing a success or error function that's within the AJAX parameters.
However, when I instead choose to not have a $.ajax({ ... success: ... }) and choose to have a .done() outside, I'm not sure how to test. Please help amend my simple spec, thanks!
Code
function itWorked() {}
function sendRequest(callbacks, configuration) {
$.ajax({}).done(function(response) {
itWorked()
});
}
Spec
fdescribe("Ajax Tests", function() {
beforeEach(function() {
spyOn(window, "itWorked")
deferred = $.Deferred().done(function() { })
spyOn($, "ajax").and.callFake(deferred)
sendRequest()
})
it("should work", function() {
expect($.ajax).toHaveBeenCalled() // pass
expect(window.itWorked).toHaveBeenCalled(); // fail
});
});
Well, probably the example in the question is not the same you are running local, but it should fail in the line spyOn($, "ajax").and.callFake(deferred) because callFake expect a function and deferred is not. Instead, deferred should be a resolved Promise and use .and.returnValue instead of .and.callFake.
Here is a working example:
function itWorked() {
console.log("It worked!!");
}
function sendRequest(callbacks, configuration) {
$.ajax({}).done(function(response) {
itWorked();
});
}
describe("Ajax Tests", () => {
beforeEach(function() {
spyOn(window, "itWorked").and.callThrough();
deferred = $.Deferred().resolve(); // Call resolve
spyOn($, "ajax").and.returnValue(deferred); // Use return value
sendRequest();
});
it("should work", function() {
expect($.ajax).toHaveBeenCalled(); // pass
expect(window.itWorked).toHaveBeenCalled(); // pass
});
});
Note that I've added console.log("It worked!!"); and use .and.callThrough(); just to double check in the console that "It worked!!" was logged.
When calling $.Deferred().resolve() you can pass a mocked response to deal with in your .done or .then callback. Something like .resolve({ success: true }). Check an example here
Hope it helps
Related
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();
});
});
});
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/
Here, _fact is a reference to the service.
it('Git Check', function() {
$scope.user = 'swayams'
var data;
_fact.Git($scope).then(function(d) {
expect(d.data.length).toEqual(4)
}, function() { expect(d).not.toBeNull(); });
});
I am getting the error
SPEC HAS NO EXPECTATIONS Git Check
Update
After forcing async as per #FelisCatus and adding $formDigest, I am getting a different error Error: Unexpected request: GET https://api.github.com/users/swayams/repos
No more request expected
The updated code snippet looks something like -
it('Git Check', function(done) {
$scope.user = 'swayams'
var data;
_fact.Git($scope).then(function(d) {
expect(d.data.length).toEqual(4)
}, function() { expect(d).not.toBeNull(); });
});
$rootScope.$formDigest();
I have a Plunk here illustrating the issue.
Jasmine is not seeing your expectations because your function returns before any expect() is called. Depending on your situation, you may want to use async tests, or use some promise matchers.
With async tests, you add an additional argument to your test function, done.
it('Git Check', function (done) {
$scope.user = 'swayams'
var data;
_fact.Git($scope).then(function(d) {
expect(d.data.length).toEqual(4);
}, function() { expect(d).not.toBeNull(); }).finally(done);
$rootScope.$digest();
});
(Note the finally clause in the end of the promise chain.)
Please note that you have to do $rootScope.$digest() for the promises to resolve, even if your code is not using it. See: How to resolve promises in AngularJS, Jasmine 2.0 when there is no $scope to force a digest?
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.
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();