Question

I am having trouble testing the returned value of a function that waits for a promise to be resolved before executing.

Javascript Method (serviceClient._getProduct returns a jQuery ajax promise object)

serviceClient.getProductName = function(id, storeId) {
  $.when(self._getProduct(id)).done(function(data) {
    return data.name;
  });
};

Test Code

before(function() {
  serviceClient.sampleResponse = fixture.load('product_response.json')[0];
  $.ajaxStub = sinon.stub($, 'ajax').returns(new $.Deferred().resolve(serviceClient.sampleResponse));
});

describe('.getProductName', function() {
  it('should return the product name', function() {
    var name = serviceClient.getProductName(serviceClient.sampleResponse.id);

    $.when($.ajaxStub.returnValue).done(function() {
      expect(name).to.equal(serviceClient.sampleResponse.name);
    });
  });
});

When I step through the call stack it seems to be executing correctly (steps inside of the promise callback in the actual js file, then steps into the test promise callback to assert), however the name variable in the test is still coming back as undefined. Any feedback would be appreciated.

Was it helpful?

Solution

You're trying to return data.name synchronously in serviceClient.getProductName, which can't be done since it depends on an asynchronous ajax request.

You're doing return data.name; inside your callback, which won't get you the expected result: if you'd return something synchronously, that return sentence should be at the scope outside that closure.

To simplify: if there's anything you can return there, it's a Deferred or Promise. It should be something like this:

serviceClient.getProductName = function(id, storeId) {
  var deferredName = $.Deferred();

  $.when(self._getProduct(id)).done(function(data) {
    deferredName.resolve(data.name);
  }).fail(function(jqXHR, textStatus, error) {
    deferredName.reject(error);
  });

  return deferredName.promise();
  // Or, if needed (I don't think so, it's resolved and rejected here)
  // return deferredName;
};

Then, in your test:

before(function() {
  serviceClient.sampleResponse = fixture.load('product_response.json')[0];
  $.ajaxStub = sinon.stub($, 'ajax').returns(new $.Deferred().resolve(serviceClient.sampleResponse));
});

describe('.getProductName', function() {
  it('should return the product name', function() {
    serviceClient.getProductName(serviceClient.sampleResponse.id)
      .done(function(name) {
        expect(name).to.equal(serviceClient.sampleResponse.name);
    });
  });
});

Leaving code aside, the conceptual mistake is that you can return name synchronously from getProductName, when that function gets the product asyncrhonously (it won't be able to access its name until the deferred is resolved).


Note: you could implement getProductName using then, that returns a Promise (which is a subset of Deferred, but you can usually get away with it and code looks even clearer):

serviceClient.getProductName = function(id, storeId) {
  return $.when(self._getProduct(id)).then(function(data) {
    return data.name;
  });
};

To remove that, also unnecessary $.when(...), you could return a Promise from _getProduct as well (if you've got a deferred, getting a Promise for it is as simple as calling deferred.promise()).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top