Moyote
Moyote

Reputation: 1217

How to test jest.fn().mock.calls in asynchronous function

I'm testing React Native component with Enzyme and Jest. I have been able to test whether mocked function is called (Alert.alert in this case), like this:

Alert.alert = jest.fn();
someButton.simulate('Press');

expect(Alert.alert.mock.calls.length).toBe(1);

And that approach have worked nicely.

Anyhow, I have a Login button, which launches a fetch. My fetch function is like this:

fetch(ipAddress, {
           ...
        })
            .then(response => response.json())
            .then((responseJson) => {
                if (responseJson.login === 'success') {
                    Alert.alert('Login', 'Logged in succesfully!');
                    console.log('i'm here');

I have mocked a fetch with promises. I added console prints to my fetch function, and noticed that they are printed within test case, like I expected. That means 'i'm here' is printed when test is run.

Anyhow, when I simulate login button press in test case, Alert.alert.mock.calls.length is zero. What I'm doing wrong here?

Upvotes: 1

Views: 2928

Answers (1)

Sebastian Rothbucher
Sebastian Rothbucher

Reputation: 1483

I didn't check this with react native, but I did write some tests for service invocations in React (I did use Flux which you didn't - but that's fine, it's the same principle in a different spot). Essentially, the promise chain is not yet done when you do the expect. This means, that both Alert and console.log are being performed after the expect, because the default promise implementation puts all next steps to the end of the event queue.

One way to overcome this is using https://www.npmjs.com/package/mock-promises - the beforeEach method in your spec needs to call install along something like this:

beforeEach(() => {
  Q=require('q');
  mp=require('mock-promises');
  mp.install(Q.makePromise);
  mp.reset();
  // more init code
});

and don't forget

afterEach(() => {
  mp.uninstall();
});

In case you don't use Q (which I did at that time), above link gives you instructions of how to install for other promises.

Now you have promises that don't put things to the end of the event queue and you can instead call the next then via calling mp.tick(). In your case, that would be something along of

it("...", () => {
  Alert.alert = jest.fn();
  someButton.simulate('Press');
  mp.tick();
  mp.tick(); // then and then
  expect(Alert.alert.mock.calls.length).toBe(1);
});

Another way, out of the box in jest is to append another then with the expects and returning the whole promise. You can find details here: https://facebook.github.io/jest/docs/en/tutorial-async.html

Basically, this is what it would look like:

functionReturningPromise = () => {
  // do something
  return thePromise;
}

// now testing it
it("...", () => {
  return /* !!! */ functionReturningPromise().then(() => {
    expect(/*something*/).toBeSth();
  });
});

In your case, this is going to be hard, however, as you don't get a handle of the promise in your test code. You could, however, split out all the fetch logic into a dedicated method (which returns the promise at least for testing) and write a test for that.

Upvotes: 3

Related Questions