Joe Gornick

How to test and wait for React async events

If you are trying to simulate an event on a React component, and that event’s handler is an async method, it’s not clear how to wait for the handler to finish before making any assertions.

Initially, one would think that because simulate simply calls the prop on the component, that it would return the result from the handler. However, simulate returns the Wrapper instance for continued chaining.

After doing some initial searches, a couple of proposed approaches would be to wait for the event loop to finish with the async event hander:

Simulate events returning promises?

it('...', () => {
    wrapper.find('...').simulate('change')

    setImmediate(() => {
        wrapper.update()
        expect(...).toBe(...)
    })
})

Wes Bos Tweet for Simulating Async Events

it('...', async () => {
  wrapper.find(...).simulate('change')

  await new Promise(setImmediate)

  expect(...).toBe(...)
})

Another approach is to use Jest’s timer mocks:

it('...', () => {
  jest.useFakeTimers()

  wrapper.find(...).simulate('change')

  jest.runAllTimers()

  expect(...).toBe(...)
})

A solution that seemed to be a little more in line with existing testing patterns is to spyOn the handler in the React component and wait for it to resolve:

const waitForSpy = async (spy: jest.SpyInstance<Promise<unknown>>) => {
  expect(spy).toHaveBeenCalledTimes(1)
  await spy.mock.instances[0]
}

it('...', async () => {
  const spy = jest.spyOn(wrapper.instance(), 'handleChange')

  wrapper.find(...).simulate('change')

  await waitForSpy(spy)

  expect(...).toBe(...)
}

The waitForSpy method first expects the spy to have been called at least once. This allows the next line to safely run waiting for the mocked instance returned Promise to resolve.

comments powered by Disqus