3

can anyone tell me how to wait in jest for a mocked promise to resolve when mounting a component that calls componendDidMount()?

class Something extends React.Component {
    state = {
      res: null,
    };

    componentDidMount() {
        API.get().then(res => this.setState({ res }));
    }

    render() {
      if (!!this.state.res) return
      return <span>user: ${this.state.res.user}</span>;
    }
}

the API.get() is mocked in my jest test

data = [
  'user': 1,
  'name': 'bob'
];

function mockPromiseResolution(response) {
  return new Promise((resolve, reject) => {
    process.nextTick(
      resolve(response)
    );
  });
}

const API = {
    get: () => mockPromiseResolution(data),
};

Then my testing file:

import { API } from 'api';
import { API as mockAPI } from '__mocks/api';

API.get = jest.fn().mockImplementation(mockAPI.get);

describe('Something Component', () => {
  it('renders after data loads', () => {
    const wrapper = mount(<Something />);
    expect(mountToJson(wrapper)).toMatchSnapshot();
    // here is where I dont know how to wait to do the expect until the mock promise does its nextTick and resolves
  });
});

The issue is that I the expect(mountToJson(wrapper)) is returning null because the mocked api call and lifecycle methods of <Something /> haven't gone through yet.

Ryan Castner
  • 954
  • 2
  • 10
  • 21
  • If it's running with jasmine, you could do "timetravelling" by using `jasmine.clock().install(); jasmine.clock().tick(1)` which should be enough for your next tick to complete. After the test, don't forget to run `jasmin.clock().uninstall()` otherwise the clock will stand still – Icepickle Oct 12 '17 at 20:54
  • unfortunately not using jasmine, using the jest expect library – Ryan Castner Oct 12 '17 at 20:58
  • Jest seems to have something similar described [here](https://facebook.github.io/jest/docs/en/timer-mocks.html#content) – Icepickle Oct 12 '17 at 21:00

2 Answers2

2

Jest has mocks to fake time travelling, to use it in your case, I guess you can change your code in the following style:

import { API } from 'api';
import { API as mockAPI } from '__mocks/api';

API.get = jest.fn().mockImplementation(mockAPI.get);

jest.useFakeTimers(); // this statement makes sure you use fake timers

describe('Something Component', () => {
  it('renders after data loads', () => {
    const wrapper = mount(<Something />);

    // skip forward to a certain time
    jest.runTimersToTime(1);

    expect(mountToJson(wrapper)).toMatchSnapshot();
  });
});

Alternatively to jest.runTimersToTime() you could also use jest.runAllTimers()

Icepickle
  • 12,689
  • 3
  • 34
  • 48
  • So this looks really promising, but unfortunately the snapshot being written is still an empty one without the UI (...) in it, I tried both the `jest.runTimersToTime(1)` and `jest.runAllTimers()` – Ryan Castner Oct 12 '17 at 21:13
  • Could you check if your api function got called? Did you try to wait like 1000 ms instead? – Icepickle Oct 12 '17 at 21:15
  • interestingly enough I added `console.error(this.state)` at the top of the `render()` function and the state IS being updated, it just seems that the `expect` statement comparing the snapshot is running too soon? maybe I need to `jest.runTimersToTime(1).then(() => // expect snapshot)` ? – Ryan Castner Oct 12 '17 at 21:16
  • If it's a promise then you might as well return it from the `it`, providing the jest setup supports promises to be returned as well :) – Icepickle Oct 12 '17 at 21:18
  • ok I did `return new Promise((resolve, reject) => process.nextTick( () => resolve(expect(mountToJson(wrapper)).toMatchSnapshot()) ))` and it worked! – Ryan Castner Oct 12 '17 at 21:22
  • please do! I was about to do that myself but I can't seem to edit my question? – Ryan Castner Oct 12 '17 at 21:23
  • @RyanCastner Wait, I just see that I misread your test, you should add the jest.runTimersToTime in front of the expect statement ;) Cause I am guessing your snapshot contains the span with bob? – Icepickle Oct 12 '17 at 21:26
  • its ok, I wanted to run the snapshot twice anyways, once for the "empty" before it loads and then after the promise resolves – Ryan Castner Oct 13 '17 at 13:52
0

As a workaround convert it from async to sync

jest.spyOn(Api, 'get').mockReturnValue({
  then: fn => fn('hello');
});
Simon
  • 26
  • 1
  • 4