Handling async behaviour in React component testing with Jest

I can guess why you’re here. You started testing your React components with Jest. Awesome! 😎 But then you started adding async behaviour to your components and now you started seeing these vague “fail” messages:

Yikes. Why isn’t this working?

In today’s post, I’ll give you some recipes for common testing scenarios that deal with sync and async component update behaviour, which include:

  • How to test sync updates to a component state (using act)
  • How to await something that we have control over (using wait)
  • How to await something that we don’t have control over (using waitFor)

We will use react-test-render to provide a lower-level understanding, but know that the test custom helpers that we’ll write often already exist in higher-level libraries such as react-testing-library.

Case 1: Test assertions on sync updates

Before we dive into async behaviour, it’s helpful to understand how sync updates work in our React testing environment.

Without any additional wrapping, you’ll find your components will encounter a race condition while trying to make test assertions on their the final state. As seen below, our expect assertion can’t find “render 2” since it is being made while the component is still updating.

Test cannot find “render 2” after an update to component state

To prevent this limbo state, React encourages us to use an act(...) method, which has the purpose of ensuring that state updates to the component have finished before we run our assertions.

Test can find “render 2” after an update to component state

With our wrapping of our state mutation inside an act, our test assertion will now be able to find “render 2” without any complaints from the testing engine.

Code example of Case 1

Case 2: Test assertions on async updates that we have control over

When async behaviour is added to our components, using an act becomes slightly more complicated.

Test cannot find “render 2” after an async mutation

Consider the case above where we mock that an async mutation is called upon a button press. Despite wrapping our update in an act, the async side effect would not be handled within our act since it exists within an outside context. This is likely what you’ve experienced recently, which has led you here.

Test can find “render 2” after an async mutation

We can capture the final render state of our component by creating a function called wait(). This function’s responsibility is to add an empty promise to the end of the execution queue, which we can then await within our act to ensure that the component has finished all side effects.

Code example of Case 2

Case 3: Test assertions on async updates that we don’t have control over

There may be some cases where we need to await things that are not explicit, such as a side-effect from a useEffect hook or an animated dropdown.

This is a situation that could benefit from us writing a function called waitFor(), which has the responsibility of polling the state of the testing environment with our set of assertions.

It can find “render 2” by polling the component

While this method is still effective, it is certainly less elegant and should only be used as a last resort when it is impossible to effectively handle your side effects using an act() or wait().

Code example of Case 3

Final thoughts

I hope that these recipes can unblock some of the frustrations that you’ve been encountering with async testing. I know the pain myself.

Remember that writing advanced testing utilities are not always the answer. If you find yourself trying to climb too big of a hill, it’s probably a good signal that you need to simplify your component structure into something that can be tested with elegance and ease.

Hello. I write primarily about programming and technology. I’m also fluent in film references.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store