React Testing Library Configuration for Productive Unit Testing | by Teddy Morin | Jul, 2022

An example with Redux and GraphQL

Photo by sharonmccutcheon on Unsplash

Too often, I join a new React project where unit tests are lacking, both in amount and quality. There are a few causes, but the one I want to discuss today is the poor test environment.

Indeed, testing requires skills, thoroughness, and is definitely time-consuming (even if that’s worth it!). If testing is more painful than necessary, it becomes a signal to avoid writing tests altogether.

With React, the tools I recommend are Jest and React Testing Library. Nothing fancy here; they are the de-facto standard in the community.

To demonstrate how to write great tests, in a good environment, we need a component to test, right? Let’s use a common functionality: a counter.

I’ll start with the following component, inside its own Counter.tsx file.

I would like to specify my application entrypoint is still App.tsx. It will be modified later in this article to demonstrate our configuration!

A complete repository can be found on GitHub.

React testing library

After following the introduction and adding jest-dom, I set up the first version of my test, as shown below:

We can find three basic tests. They help us verify we’ve displayed a default value, which increments or decrements when the corresponding button is clicked.

With the current configuration, I’m able to run my test successfully:

But issues arise when working with a bigger codebase, more functionalities, and dependencies. In this article, I would like to demonstrate how we handle Redux and GraphQL which are fairly common.

Adding Redux

I’m following RTK Quick Start, which conveniently shows an example with a counter app. I end up with the following slice:

The following store:

And an updated Counter.tsx component:

The behavior of my counter didn’t change, but tests are failing with the following error message:

Could not find react-redux context value; Please ensure the component is wrapped in a .

It’s quite straightforward. We are trying to test a component in isolation, but it needs a react-redux provider to work. I added it to my App.tsxbut from my test perspective, it’s nowhere to be seen.

Now, for every test, we need to declare a new store and render our component with the Provider from react-redux. Technically, it would work with the following code:

But, is that the right solution? That’s a lot of unneeded boilerplate code. If you ever have more dependencies, your tests will grow exponentially. It makes them hard to write and maintain.

Instead, React Testing Library explains how to set up a Custom Render.

Setup improvement

After following the Custom Render section, I end up creating a tests/ directory with a testing.tsx file:

I add an index.ts file that re-exports everything from my tests/ directory Then, instead of importing my Redux boilerplate code and utilities from @testing-library/reactI import utilities from this directory:

That’s much better! But, what if we need to trigger some change to our Redux store during our test? Also, when our app grows, adding dozens of providers inside our testing.tsx will make it harder to read and maintain.

First, I want to make my render more customizable. If you look at the customRender method, you can see it takes some options related to React Testing Library.

We can use those options to customize our providers. I’ll allow a new property, providers, which is an object with the data related to our providers. For now, it takes the following:

The implementation looks like this:

Looks good, I’ll even throw in a helpers function to build a store:

This way, I’m able to write a more advanced test:

That looks quite good! But we still have one issue: the testing.tsx file will grow to become unmaintainable with the current state of things.

Instead, I would like to move the declaration for providers to different files, and build the function allTheProviders on the fly. Also, there is a great pattern to build it: function composition!

Let me explain. Instead of having one huge function, I create one for each provider I want to add. These new functions take options, a React node, and return a React node (with potentially a new provider).

This function, for Redux, would look like this:

If they have this structure, I can chain them, or in other words, compose them, to build a component with multiple providers. If I split my list of providers, the function is dedicated to composing them, and the function AllTheProvidersit looks like the following:

And that’s it! This way, we can declare new (composable) provider functions, in a different file, and add them to our list.

Adding GraphQL

Let’s improve our demonstration by adding GraphQL. We’ll add functionalities to load and save the current counter.

My schema and resolvers look like the following:

After setting up Apollo on the server and frontend, I added two queries to my counter:

Then, I updated my Redux slice, and added two buttons in order to save and load the current counter:

But now, just like for Redux, our tests throw an error:

Invariant Violation: Could not find “client” in the context or passed in as an option. Wrap the root component in an , or pass an ApolloClient instance in via options.

Let’s follow the testing section from Apollo, and integrate it into our custom render. We can start by adding an option for GraphQL mocks and create a composable test provider for apollo:

Then, we can add this composable provider to our providers. Keep in mind the order of providers will have an impact on how our providers are added.

This is related to how function composition works. The most left in the list will be the inner providerswhile the most right will be the outer providers.

Then, I’m able to write the following test:

And that’s it! You are now able to write proper tests in a healthy environment. Moreover, you won’t have any issues when your app gets bigger, as long as you continue to create composable test providers.

Don’t forget a complete repository is available on GitHub.

Thanks for reading.

Leave a Comment