Hommer Smith
Hommer Smith

Reputation: 27852

Can't test effects of resolved promise

I have the following code in user:

 import { redirectTo } from 'urlUtils';
 export function create(user) {
    api.postUser(user).then((response) => {
      redirectTo(response.userUrl);
    })
 }

And I have the following test:

import * as api from 'api'
import * as user from 'user'

sinon.stub(api, 'postUser').returns(
  Promise.resolve({ userUrl: 'a-url' })
);
sinon.spy(urlUtils, 'redirectTo');
const userData = {id: 2};

user.create(userData);
expect(api.postUser.calledWith(userData)).toBe(true);  // This passes
expect(urlUtils.redirectTo.calledOnce).toBe(true); // This fails

I have been able to test it on the browser, and the redirect is happening. What am I missing here? I have stubbed the request call to resolve the promise synchronously, so that shouldn't be a problem.

Upvotes: 0

Views: 57

Answers (1)

Lennholm
Lennholm

Reputation: 7470

Promises are asynchronous, so when you're doing expect(urlUtils.redirectTo.calledOnce).toBe(true) the promise hasn't been resolved yet.

The easiest way to get around it is to wrap that expectation in a short timeout and then use whatever utility the testing framework you're using provides to signal that an asynchronous test is complete. Something like this:

setTimeout(() => {
  expect(urlUtils.redirectTo.calledOnce).toBe(true);
  done();
}, 5);

Another, nicer solution is to actually use the promise that your stub returns. First, keep a reference to that promise:

Replace:

sinon.stub(api, 'postUser').returns(
  Promise.resolve({ userUrl: 'a-url' })
);

with:

const postUserPromise = Promise.resolve({ userUrl: 'a-url' });
sinon.stub(api, 'postUser').returns(postUserPromise);

then write your expectation like this:

postUserPromise.then(() => {
  expect(urlUtils.redirectTo.calledOnce).toBe(true);
  done();
});

done() is the function most test frameworks (at least Jasmine and Mocha as far as I know) provide to signal that an asynchronous test is completed. You get it as the first argument to the function your test is defined in and by specifying it in your function signature you're telling the test framework that your test is asynchronous.

Examples:

it("is a synchronous test, completed when test function returns", () => {
  expect(true).to.equal(true);
});

it("is an asynchronous test, done() must be called for it to complete", (done) => {
  setTimeout(() => {
    expect(true).to.equal(true);
    done();
  }, 5000);
});

Upvotes: 2

Related Questions