Reputation: 10215
I'm trying to write a Jasmine test for a module that uses a jQuery ajax wrapper library named jquery.rest
This module under test:
var module = function() {
function getData(callback) {
IP.read().done(function (data) {
console.log("done");
callback(data);
});
}
return {
getData: getData
}
}();
The client
and IP
variables are declared in a different file, like so:
var client = new $.RestClient('/rest/api/');
var IP = client.add('ip');
I would like to mock the read()
function so that it would return a Json payload that I define in my test.
The read()
method returns a $.Deferred
object.
I have tried different approaches (using Jasmine spies) but without success.
Upvotes: 0
Views: 959
Reputation: 7634
I see two ways to do this:
spy $.ajax()
and call fake function that returns your own deferred
Contra: you indirectly test the library
mock $.RestClient
s interface and return your own deferred
Contra: more work to mock the library when not only testing the callback is required. (The more complicated your mocking the more error prone your test is.)
TL;DR Skip this if this is known.
But first let's look at how RestClient works... It has two basic objects, a Resource and a Verb. The RestClient is actually a Resource
object (1). Resource
objects will return another Resource
object when add()
ing a rest fragment (2). The pre-defined verb read
will return a Verb
instance's call
method (3).
From the bottom to the top of that chain a request
method is accessible from the call()
Method (4). If not explicitly overridden it defaults to $.ajax()
. (5)
If not configured differently, a call to read()
will result in a call to $.ajax()
, returning a promise.
So, when doing new new $.RestClient().add("...").add("...").read()
you'll get what you'd get with $.ajax()
.
Variant 1:
describe("getData()", function(){
// Handle to ajax()' deferred, scoped to the
// describe so the fake ajax() and the it()
// have access to it
var def,
underTest;
beforeEach(function(){
// Mock $.ajax (or what a Verb calls in the end)
// assign "def" with a deferred and return it,
// the test can then resolve() or reject() it
spyOn($, "ajax").and.callFake(function(opts) {
def = $.Deferred();
return def;
});
// This is under test
underTest = new UnderTest();
});
afterEach(function(){
// Ensure a missing call of ajax() will fail the test
def = null;
});
it("should call callback on successful read", function() {
var callback = jasmine.createSpy("callback");
// Indirectly call ajax() which will create def
underTest.getData(callback);
// Resolve the deferred to succeed the response
def.resolve({a: 1});
expect(callback).toHaveBeenCalledWith({a: 1});
});
it("should not call callback on failed read", function(){
var callback = jasmine.createSpy("callback");
underTest.getData(callback);
def.reject();
expect(callback).not.toHaveBeenCalled();
});
});
The fake is returning a deferred, not a promise, but in this case it's OK since it has the same interface, and nothing/no one should reject or resolve the deferred here except us.
Variant 2:
describe("getData()", function(){
// Store original reference
var origRestClient,
// See first code block
def,
underTest;
// Mock thr Resouce object
function MockResource() { }
// Simplify the behaviour of this mock,
// return another Mock resource
MockResource.prototype.add = function() {
return new MockResource();
};
// What Verb.call() would do, but directly
// return a deferred
MockResource.prototype.read = function() {
def = $.Deferred();
return def;
};
beforeEach(function(){
// Replace RestClient
origRestClient = $.RestClient;
$.RestClient = MockResource;
underTest = new UnderTest();
});
afterEach(function(){
// Restore RestClient
$.RestClient = origRestClient;
def = null;
});
it("should call callback on successful read", function() {
var callback = jasmine.createSpy("callback");
underTest.getData(callback);
def.resolve({a: 1});
expect(callback).toHaveBeenCalledWith({a: 1});
});
it("should not call callback on failed read", function(){
var callback = jasmine.createSpy("callback");
underTest.getData(callback);
def.reject();
expect(callback).not.toHaveBeenCalled();
});
});
The mocking of Resouce
needs more work than what I've done if you'd like to test the path and request data too, with the above code this is not possible.
Upvotes: 1