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,
Copy the code that enables this render composition,
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.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 );
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.