Reputation: 197
I have the following functions:
function getPersonData(id) {
retrieveData(
id,
function(person) {
if(person.name) {
displayPerson(person);
}
}
}
function retrieveData(id, successCallBack) {
executeRequest(id, {
success: successCallBack
});
}
getPersonData
retrieves a person's information based on the id. It in turn calls retrieveData
by passing in the id and a successCallBack function.
retrieveData
takes the id and successCallBack and calls another function, executeRequest
, which gets the data and passes back a person object.
I am trying to test getPersonData
and have the following spec set up
describe("when getPersonData is called with the right person id", function() {
beforeEach(function() {
spyOn(projA.data, "retrieveData").and.returnValue(
{
'name': 'john'
}
);
spyOn(projA.data, "displayPerson");
projA.data.getPersonData("123");
});
it("displays the person's details", function() {
expect(projA.data.displayPerson).toHaveBeenCalled();
);
});
But when the spec is executed the displayPerson
method isn't called. This is because the person data being passed back from the success callBack function(person)
isn't being passed in even though I have mocked retrieveData
to return a result.
My question is: Is this the right way to test callBack functions? Either way what am I doing wrong?
Upvotes: 2
Views: 4609
Reputation: 2064
Ok, so jasmine is tricky in a lot of subtle ways and I think there's two main issues with your code
retrieveData
function which just calls executeRequest
function with the exact same parameters but in a slightly different way.I rewrote your getPersonData to be like this
function getPersonData(id) {
// this is needed to properly spy in Jasmine
var self = this;
//replaced retrieveData with just execute request
// self is required to properly spy in Jasmine
self.executeRequest(id, {
success: function(person) {
if (person.name) {
self.displayPerson(person);
}
}
})
}
//I don't know what exactly executeRequest does
//but I took the liberty to just make it up for this example
function executeRequest(id, callback) {
callback.success({
name: id
});
}
//I also assumed that projA would look something like this
var projA = {
data: {
getPersonData: getPersonData,
retrieveData: retrieveData,
displayPerson: displayPerson,
executeRequest: executeRequest
}
};
2. In order to test asynchronous code in Jasmine, you need to include a done
callback with the test. Also, if you expect a callback function to fire automatically, you need to set it up within a setTimeout
function, otherwise it will never fire. Here's an adjusted example:
describe("when getPersonData is called with the right person id", function() {
beforeEach(function() {
//you should not spyOn retriveData or executeCode because it will override the function you wrote and will never call displayPerson
// you should only have spies on the methods you are testing otherwise they will override other methods
spyOn(projA.data, "displayPerson");
});
it("displays the person's details", function(done) {
// it's better to call the function within specific test blocks so you have control over the test
projA.data.getPersonData("123");
// at this point, it will just have called the getPersonData but will not call executeRequest
setTimeout(function() {
//this block will just call executeRequest
setTimeout(function() {
//this block will finally call displayPerson
expect(projA.data.displayPerson).toHaveBeenCalled();
//tell jasmine the test is done after this
done();
})
})
});
})
Upvotes: 3