React Native tests that would (hopefully) make Kent C. Dodds proud

Tanner West
5 min readApr 28, 2022
Photo by Dan Meyers on Unsplash

When I set out to learn React Native testing, I didn’t know where to turn for best practices regarding unit and integration tests. The React Native codebases I’d worked in professionally either relied strictly on automated E2E tests with Detox or on outdated an hard-to-maintain Enzyme tests (you should never write React Native tests in Enzyme anymore, by the way).

I selected React Native Testing Library and Jest as my preferred testing tools. React Native Testing Library because it’s easy to set up and naturally lends itself to the practices I’ll describe below, and Jest because it comes already set up with projects generated with npx react-native init .

With my tools ready to go, I still needed a methodology to guide the structure and focus of my tests. I was aware that Kent C. Dodds was a thought leader in the React space and that his approach to writing tests had become well known in the JavaScript community, so I turned to his blog and talks to seek guidance for how I might write React Native tests. Here are some approaches that I walked away with.

Limit tests to the parts of your component that actual users are aware of

One of the core principals of Kent’s methodologies is to never test implementation details. In the context of React components, implementation details would include state variables, event handlers, and other technical details that users never see.

To avoid test implementation details, a good rule of thumb is to test things that your users “use, see, and know about.” A convenient way to constrain yourself here is to test only the code in the render block of your component.

Consider the following component from an AWS certification exam study app I’ve been working on:

The user selects their answer, presses “Submit” and then they’ll see either a “Correct” or “Incorrect” card appear at the top of the screen. This function component’s returns the following:

To write a unit test for this component, I decided to test for these criteria:

  • The component renders the question text
  • The component renders 4 answer options
  • The component renders the “Submit” button
  • After the user selects an incorrect answer and presses “Submit,” the component renders an “Incorrect” card

If these criteria are met, I can feel confident that my users will have the experience I intend them to have with this component.

Here’s what the test looks like:

I’m using the UI Kitten component library in this app, which requires the <ApplicationProvider /> you see wrapping our component here.

As you can see, React Native Testing Library makes writing these kinds test feel very natural. They’re not nearly as verbose as the same tests would be in Enzyme. The getByText() and fireEvent() functions make it a breeze to test only what the user could see and interact with, which is exactly the approach that Kent suggests.

Devote most of your time writing tests to integration tests

The kind of unit test we just saw are easy to write, but their downside is summarized in another of Kent’s well-known concepts, “testing trophy”:

His argument is basically that integration tests (i.e. tests that “integrate” several units [or components in our case] and test whether they interact [as a whole screen or feature] as we expect) provide the best balance between the ease of writing/running them and the confidence that they give us.

Let’s look at the parent component of the Question component we tested above to see how such an integration test might look in our app.

The parent component Question is QuizScreen , which takes users through a series of questions as seen in the screenshot above until all questions are answered. Then it displays a Results component like this:

QuizScreen ‘s return block looks like this:

I landed on the following criteria for the integration test I wanted to write:

  • The user should select a correct answer, press submit, and see the “Correct” card
  • The user should select an incorrect answer, press submit, and see the “Incorrect” card
  • The user should press “Next” after answering the last question and see the results screen

This is what my test looks like:

Notice that, in addition to wrapping our screen component in <ApplicationProvider /> as we did in our unit test, we must also wrap it a redux store <Provider /> because QuizScreen gets its list of questions from the redux store. Luckily, the state we need for this component is simple, but in larger apps, getting the right state to your component can be one of the most cumbersome parts of writing these types of unit tests. Still, I agree with Kent that this type on integration test can provide the highest return on investment because they most accurately reflect how actual users will interact with your app.

If we wanted to follow the “Testing Trophy” guideline more closely and invest more time integration tests, we could forego unit tests like the one above altogether, and combine the test cases from it into our integration test. That would be totally doable.

Key takeaways

  • React Native Testing Library is the perfect tool for writing the kinds of tests that Kent C. Dodds suggests because its API naturally steers you towards testing parts of your component that users see and interact with
  • When possible, focus on writing integration tests that test an entire screen or feature’s worth of functionality. These tests can give you the most confidence because they best reproduce the experience your users will actually have in your app

--

--