user12222
user12222

Reputation: 197

Testing JavaScript callback functions with jasmine

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

Answers (1)

superjisan
superjisan

Reputation: 2064

Ok, so jasmine is tricky in a lot of subtle ways and I think there's two main issues with your code

  1. You have way too many asynchronous calls wrapped in each other. Which is by itself not a problem, but it makes testing in JASMINE hell of a lot harder. For example, what is the point of having a 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

Related Questions