Declarative testing patterns for React Context

Using react testing library to test React context

When Context API was announced the React ecosystem was taken by storm. There was now a native solution to solve the problems of prop drilling, state management, etc. Everything was great!

Except, testing it.

Testing components (using react testing library or otherwise) that were context consumers was a real pain, because of the sheer effort required to setup context in an isolated unit test suite. Not to mention the amount a boilerplate code required to set it up, which added no value to the reader of that test, including the author itself.

Consider the below React app with the 2 context providers UserProvider and SessionProvider,

Somewhere deep in the component hierarchy these contexts are consumed by the Level2 component,

The hooks useUser and useSession internally useContext hook. This way of consuming context is popularized by Kent C Dodds in this blog post.

If you wanted to test Level2 component following the approach in official testing library docs you can do so like this,

So what is wrong with the above code?

Nothing!

But imagine if components in your app consume more than 5 context providers and some of them needed custom context values in your tests. How would this serve you? Not very well. It would require you to create this wrapper tree for each of your test suite, which is not very expressive.

Another extreme is creating a render helper that always has all the providers in the app and using that for testing every component. This is also not ideal, because your component renders differently than it does in your real app so it reduces the confidence that your tests give you. This also makes passing custom context values to your context providers messy and your render-all-context-providers helper becomes a victim of too much configuration.

It would be nice to have a middle way,

This way, you still have to create a helper render method, but it is so lean! Passing custom context values is done via the withProps helper. Perfect balance of expressiveness and configuration! 💯

How to do this?

There is a one time setup involved,

  1. Copy the code that enables this render composition,

  2. Create test wrappers for your context providers

    The ones that need custom context values can allow so withProps pattern. This is needed only once for every context provider.

  3. In your test suite, use composedRender from step #1 and test wrappers from step #2 and create a render function that is decorated with a tree of the context wrappers.

       const render = composedRender(
          UserProviderWrapper.withProps({ value: { name: "Bernie" } }),
          SessionProviderWrapper
       );
    
  4. In your test suite, use render like you would use it if it was imported from @testing-library/react

       describe("<Level2 />", () => {
          test("should display logged in user", () => {
             render(<Level2 />);
             screen.getByText(/Logged in as Bernie/);
          });
       }
    

You can see all of the above working together on this codesandbox.

And that’s it, quick tests at the cost of a very small setup! If you have other patterns that work well, do share them with me.

Bhargav Shah
Bhargav Shah
Front End Developer

I’m passionate about writing code that makes user lives easier.

Related