Microblog — Jest Test Patterns for React Native

Benjamin Gowers
5 min readAug 27, 2021

I haven’t written in a little while and was thinking that it would be good to start a series of microblogs. This is mainly for my own benefit so that I can keep track of some of the interesting things that I’ve done during my Software Development journey. Most of these microblogs will be about React Native with other tech integrations.

Any passers-by are welcome since you might find these short reads interesting too!

Intro — Structure Your App for Testing 👷

Something I love about React is the freedom that you get when building an application. You can architect it in any way you like. It’s very important to standardise the way you build an application so that you don't get lost in a mess of components, custom hooks and state management (among other things). I won’t talk about architecture in this blog, but it’s important to think about it in the context of testing since keeping things modular and independent makes testing much easier.

On a recent greenfield project, I loved the way that integrated tests were used to test components, so I thought I’d share this testing pattern with anyone interested!

Let's talk about the two main types of component in this project along with nice ways to test them.

Atomic (dumb) Components — Snapshots 📸

Taking inspiration from the atomic design pattern, we have Atomic Components or Atoms. These are the smallest possible components, such as buttons, cards, text, etc. They should also be dumb — contain minimal state (e.g. disabled, hover) and be purely presentational (no interactions or business logic). Given these criteria, we know that our tests aren’t going to be complex, hence, we can simply render the component using react-native-testing-library, then snapshot and be done with it!

Here’s an example of a test for a custom TertiaryButton component that uses a TouchableOpacity along with styles and enabled/disabled state.

Super easy! Now on to the more interesting components

Complex (smart) Components — Existence and Functionality 🧠

Smart components usually render two or more child components that need managed state. For example, a user presses a button and something on the current view updates (notification, style changes to another component, etc). With this in mind, there are two things that we should test for in smart components — the existence of child components and any core functionality that the smart component should have (e.g. button press actions).

Existence of Child Components

Since a smart component might make up the majority of a screen/page in your app, we want to know that our smart component is rendering the correct children. You might think that snapshotting would be a good way to track this, but this is not the case. Imagine if we have a complex component that does a few API calls and manages many local states. Now we would have to snapshot the component under every possible state in order to see each of the possible renders. Plus, if we make a change to one of the atomic components, not only would we have to update the snaps for that atomic component, but every complex component that uses it.

So instead of snapshotting, we’ll check for the existence of the element that we want to see (optionally under certain conditions). Using react-native-testing-library, we can do this in a few different ways (using the get/query/findByXxx functions). I don’t recommend adding test IDs to atomic components in order to find the component, because this prop doesn’t add any value to the component, other than for testing. A better way is to find a component by its text or accessibility label.

Here’s a very simple example of a child component existence check within a smart component that renders many other components.

Menu Icon Existence Test

There’s a nice isolated test for the existence of an atomic component, within a complex component.

Functionality

Testing the functionality of our smart components ensures that the same behaviour persists if we do a refactor.

More often than not, we will need to interact with a component within our test (e.g. press a button). This is beautifully handled by the fireEvent function from react-native-testing-library.

Let’s take a look at an example.

Home Component Button Press Navigation Test

Here we’re testing a button on our home page that, when pressed, navigates to a Settings screen, nested within a Connection navigator. I have mocked out react-navigation, specifically the navigate function from the useNavigation hook. Then we simply render the component, find the button, press it and check that navigate was called with the correct parameters.

A Note on Mocking External API Calls 🔁

In this project, we use Wretch (a lightweight fetch wrapper) for making external requests and React Query for managing the request/response state.

If we have a component that requests external data (e.g. when it mounts or after some interaction), we should mock the external endpoint so that we can manage the exact data that our component has access to.

For this, we use a package called Nock — a lightweight HTTP server mocking package. We simply write the nock mock at the top of our test, then test the component without needing to mock anything from Wretch (or fetch) or React Query.

Login Error Test

Stepping through this test:

  1. We pass nock our route and HTTP method and mock an error response (in the same shape that we expect from that endpoint).
  2. We render our component, fill out text fields and press the login button (which makes the request to ).
  3. Since Formik uses asynchronous behaviour to update the form state, we force it to run all Promises by awaiting an ‘immediate’ Promise (setImmediate forces Promises to run on the next event loop).
  4. While the mocked request is being sent, we check that the button is in a Loading state.
  5. Since we have mocked a failed login request, we check that our UI reflects this by searching for our error text (in an error card component).

P.S. await component.findByText(…) is equivalent to await waitFor(() => { component.getByText(…) })

Thank You! 👏

Thanks for reading, there’s much more to come! Looking forward to seeing you there.

--

--

Benjamin Gowers

Software Developer @ Kooth, Former Organiser @ React Native London. I love learning and letting other people know what I’ve learnt!