An introduction
Testability of Redux reducers is well known, because of their pure functional nature, however proper testing of middleware like Saga or Thunk is somewhat more complex. Additionally testing all of these parts separately does have two major problems:
- Each of them might work correctly in separation, but it’s not guaranteed when they are assembled
- Tests become dependent on implementation details of reducers, sages, etc.
This blog post describes how you can write integration tests for React Native apps with Redux, using React Native Testing Library. The setup described here allows us to test all application parts together, in conditions resembling normal app operation when components are connected to the Redux store with attached middleware.
Read more: In case if you missed it - React Native Testing Library has just merged with Testing Library - read the article to find out more about it.
As part of The React Native Show, we released a podcast fully dedicated to future of React Native Testing Library.
Setup
Redux store setup
In order to provide a Redux environment consisting of store and middleware, we first build a function for assembling the store. It's a part of your application code, and you should use this function to build the store used by your app. We are going to use it to create a separate store for each test so that our tests work in isolation and do not interfere with each other.
Custom render function using React Native Testing Library
By using the <rte-code>buildStore<rte-code> function, we can write a custom <rte-code>renderWithRedux<rte-code> function that will render our components inside a Redux Provider so that we can test connected components. We will use React Native Testing Library to achieve that. It provides us with convenient helpers to render our components, interact with them, and assert on their output.
Basic integration tests
Finally, we are able to write our integration tests. As our components render with real store and middleware attached, we need to mock API calls so that the middleware does not call real API.
For simplicity, we mock our API calls directly inside the tests. But for more complex apps you'll likely want to come up with some abstraction around your back-end server responses. This will allow you to decouple your test cases from networking, making them even less prone to rewrites in this area. Read more in the Stop mocking fetch blog post.
The benefit of this approach is that our tests do not contain any reference to our Redux or middleware implementation details. We only mock the API calls, and yet we are able to test the whole LoginScreen component without caring about state management libraries used.
Tests with the initial state
In our tests of LoginScreen component, I assumed that we do not need any initial state in our Redux store. However, frequently our components will assume that some state is present, e.g. home screen for a logged-in user could assume that some basic information about the user is available in the store. You can pass such state in the following way:
Other global dependencies
If you have other dependencies like navigation or internationalization libraries, they should also be included in your custom render function in a manner resembling your <rte-code>App.js<rte-code> setup.
Summary
You can easily setup integration tests for your React Native + Redux application. Integration tests are a great way of automating manual test scenarios, especially for major happy and error paths. They cover almost all pieces involved in the tested screen, without need for separate component, reducer & middleware unit tests. In case you have some edge-case logic in your reducers, unit testing is highly encouraged.
Happy testing!