In frontend development, testing user behavior is crucial for delivering great products. React Native Testing Library (RNTL) makes it a joy to test user journeys in our React Native apps. It has great APIs to make tests immune to implementation details.
By the end of this tutorial, I promise you will feel this joy, too — and I won’t take up too much of your time getting there. You can find the full code for this demo on GitHub.
Let’s begin with adding libraries that we will be using to make our testing a joyful experience.
yarn add -D @testing-library/react-native
One thing to note here — RNTL is a wrapper over react-testing-library that depends on Jest. This combination, in my humble opinion, is a match made in heaven and makes the testing experience amazing.
Button
componentLet’s start small; if you are familiar with testing components, you can skip this section. We will test a simple button component just to make ourselves a bit more familiar with how RNTL works and what we are up to. Here is how our Button
component looks:
Here’s the full code for the Button
component. We will test that it should call the onPress
method when it is pressed by a user. Our test should look like this:
RNTL provides a great API to grab components using attributes. Though there are a number of ways to grab a component, I feel getByTestId
makes your test code more immune to UI and textual changes.
With the fireEvent
API, we can mock many interactions, which allows us to test user behaviors. Here, we have created a mock method and asserted that it should be called only once when a user presses the button. We will use a similar structure for testing our flow further.
Here is how we will execute this test, along with the expected output:
Cool, so we have done some basic testing now and have a good idea of how things will shape up.
Let’s take a step forward and practice some real-world examples. We will test a form that takes email and password inputs from a user and passes them to the onSubmit
method. We will also assert that it renders a validation error if a user has not entered a password. Here’s our component:
Let’s test our first use case: the component should display a “password required” error. Here’s our test will look:
Here we are testing an action that has an impact on state. Since setState
is async in React, we have to execute this test in an async way. RNTL provides the waitFor
API for this. Also, we have used the same fireEvent
API here for changing the text of our input component.
Moving to our next use case, we expect to call onSubmit
with email and password:
We’re taking another step forward by asserting the mock function’s argument to make sure we have the expected output. Here is our full test suite for the EmailPasswordForm
component.
Now that we’ve built up some confidence, let’s test our complete login flow. This isn’t just about testing methods being called. Login usually includes two main functions: it has some network API call that returns a token, and then we save that token in local storage for later use.
In order to make our test independent of server/network, we will mock our API hits. This is possible with the fabulous open source package fetch-mock-jest. It intercepts all fetch
calls and resolves them with given response. This allows us to test all success and error cases. Let’s add it to our code base.
yarn add -D fetch-mock-jest
Here is the simplest mocking we will use; it mocks a login API call with an expected response.
AsyncStorage
React Native apps use a number of native bridges to access platform-specific functionalities. In our case, we have to make sure the login API response is stored in local storage.
RNTL allows us to mock these native bridges so we can run our tests without depending on devices or emulators. In order to mock these libraries, we will add the file jest.mock.js
, which will code for mocking all of the native libraries we are using in our app. Here’s our mock file:
jest.mock('@react-native-community/async-storage', () => ({ setItem: jest.fn(), }));
One important point to note here: we have to mock all methods that we are using from libraries, as these are not added in our testing environment.
Now we will reference this file from our jest.configs.js
file:
module.exports = { ... setupFiles: ['./jest.mock.js'] ... };
Adding this file to the setupFiles
array will tell Jest to execute this file before running any test suite.
Now that we have our mocking in place, let’s take a look at how we’ll test our full flow:
Don’t worry — it’s just lengthy, not hard to digest. We already know most of this.
To begin with, we have added fetchMock
in our beforeAll
hook to make sure our fetch mocking begins before this test suite starts. Here we have rendered our screen component with a mocked navigate method; this is how our screen will be rendered in the app.
After executing all user interactions, we start asserting things happen as we expect. Here is the list of our expectations:
jwtToken
is stored in local storageIf all of these tests pass, we are sure that our login screen works the way we expect it to. This marks our login flow testing as complete.
Testing might take a bit longer to execute over time as our test suites grow in number and size. Linting our code before executing any tests will make sure we are not executing tests on code with any obvious bugs. We will add lint tasks in the pre-test script. Here’s what it looks like:
You can view the full package.json
file here.
Who doesn’t like charts and dashboards to visualize performance? Here, we will integrate the Jest test reporter, which will deliver us a complete picture of our test execution. This will help us monitor what tests are breaking and how much time each test takes to complete.
Adding the reporter to our project:
yarn add -D jest-html-reporters
Next up, let’s add this reporter to our jest.configs.js
file. Here’s how it looks:
To get it running, let’s open up this reporter automatically every time our tests are executed. It’s simple — we’ll add it to our post-test script as an update to our package.json
:
Here’s the test report for our project:
And here’s the .html file of the report.
For large-scale applications (and side projects that we work on every seventh day), it’s better to have solid checks in place to maintain code quality. These checks must be enforced to ensure that every PR that arrives in our repos is up to standard and passes defined criteria.
Pre-commit hooks can help us here. These hooks run before every commit. We can add our test task to make sure that no commit is allowed to break any of our tests. We are using husky here, which makes adding commit hooks much easier. This is our last library to add:
yarn add -D husky
Let’s set up our first pre-commit hook here. npx will help us with that:
npx husky add pre-commit "yarn test" # will create .husky/pre-commit file
Running this command will create a .husky/pre-commit
file for us. It’s a bash script that will be executed before any commit.
At times, our list tasks have a --fix
option that makes changes to our files as we run the command. In our case, we added a Jest reporter in the section above, which will create a new reporting file as we run our tests. In order to add all of these changes to our commit after pre-commit hooks are executed, we will update our pre-commit file. This is how it looks:
Yes, I know how it feels when you have a complete testing ecosystem in place — more relaxed and confident that your code won’t break too soon.
React Native Testing Library makes it easier to follow best practices and keeps your testing experience joyful. It allows you to test your application in the same ways your users interact with it. Making sure all your use cases are covered provides a lot of confidence — you better believe it will get your app’s rating up.
Moving forward from here, you can try out Detox for end-to-end testing; it will help you test for cases that require native APIs, like notifications, deep linking, and sophisticated user interactions.
LogRocket is a React Native monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your React Native apps.
LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.
Start proactively monitoring your React Native apps — try LogRocket for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]