Editor’s note: This article was last updated on 9 August 2023 to provide more information about snapshot tests and mocking external API requests.
React Native is among the most commonly used libraries for building mobile apps today. In this guide, we will explore unit testing in React Native applications, covering benefits, best practices, testing frameworks, and more.
Jump ahead:
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
Unit testing is the practice of testing small, isolated pieces of code, including functions, classes, and components. We can test each of these individually to ensure they work.
Consider the example below, a snippet from a utils.js file in a JavaScript project:
// utils.js
function toJSON() {...}
function convertToSLAs(args) {...}
function hourToMins(hour) {...}
export {toJSON, convertToSLAs, hourToMins}
The file contains functions that perform different actions. We can unit test each function in this file, meaning we can test each of the functions independently across different conditions to make sure they each work as intended:
// test/_tests_.js
describe("toJSON tests", () => {
it("should return object", () => {
expect(toJSON(..)).toBe(...)
...
});
...
});
describe("convertToSLAs tests", () => {
it("should return true", () => {
expect(convertToSLAs(...)).toBe(true)
...
});
...
});
describe("hourtoMins", () => {
it("should be truthy", () => {
expect(hourToMins(...)).toBe(true)
...
});
...
});
As you can see, we wrote tests for each function. Each test suite contains a number of tests that completely cover the work of the function.
Testing frameworks like Jest, Mocha, Jasmine, and others provide detailed results from the tests. Any tests that fail the conditions are marked as failed, indicating they’re in need of further examination; of course, the tests that pass are marked as passed.
Think of a failed test as a good thing. When a test fails, it usually means something isn’t right, which gives us an opportunity to fix the problem before it impacts our users. Unit tests are great for giving quick feedback and providing confidence that our functions will work as intended in production.
Code written with testing in mind usually adheres to best coding practices as it tends to be loosely coupled and more modular, which improves overall code quality. Writing unit tests encourages developers to write modular and well-organized code.
Unit testing checks specific parts of the code and enables developers to identify and fix errors before they become more significant or before the code is deployed. In turn, this makes the codebase more maintainable and ensures that it is of high quality.
Unit tests are a great way to document as they serve as living documentation for your code. Unit tests provide concrete examples of how your code is supposed to work and are useful for other developers who may need to go through your code for whatever reason. This documentation is valuable for when other developers need to understand and work with your code or when you revisit your code after some time.
With unit tests, bugs are found early in the software development process. This makes it easier to fix the bugs or problems and prevents these issues from moving to the production or release stage of the development process. This saves the amount of time that would have been spent on debugging.
Unit testing enables developers to write reliable code by identifying and resolving bugs early. This maintenance can be done by anyone who will quickly understand the code thanks to the existence of the unit tests.
Unit tests focus on testing individual components or units of code in isolation. You can use testing libraries like Jest, Enzyme, or React Testing Library. Unit tests help ensure that each component behaves as expected and that specific functions work correctly.
Integration tests provide a more holistic view of system behavior as they focus on testing the interactions and integrations between different components or modules of a system to ensure that they work together as intended. They verify the interactions between various components and modules within the app, ensuring that the integrated parts of the app work well together. Integration tests for React Native can be written using testing frameworks like Detox.
Snapshot testing is a powerful testing method used in React Native and in other Javascript-based frameworks to ensure the consistency of UI components on different renders over time. It typically involves taking a snapshot of a rendered UI component and saving it as a reference point. At a later time, the test is rerun, and the new output is compared with the previously saved snapshot. The stored snapshot is usually how the UI should look according to the developer’s expectations. If there are any differences between the two, the test will fail, indicating that the UI has changed unexpectedly.
Here’s the process flow for snapshot testing:
react-test-renderer or @testing-library/react-nativereact-test-renderer. This utility renders the component into a virtual DOM and provides you with methods to interact with and inspect the rendered output. When you run the test for the first time, a snapshot of the rendered component’s output is generated and saved in a designated file--updateSnapshot or -u. This overwrites the existing snapshot with the new outputE2E testing involves testing the entire application workflow, mainly from a user’s perspective, to ensure all app parts work together correctly, including the UI and navigation. It simulates real user scenarios and aims to validate that the entire application behaves correctly, including its user interface and underlying functionality. Tools like Detox and Appium can be used for E2E testing in React Native.
Mocking external API requests in React Native tests involves simulating the behavior of actual API calls without making genuine network requests. This enables you to test components or features in isolation, control the data and scenarios you want to test, and eliminate dependencies on external services.
To mock API requests, you first need a mocking approach. You can decide to create your mock functions using JavaScript’s built-in mocking features, or you can use a mocking library. There are several mocking libraries that can be used in React Native, including:
Tests should be readable and maintainable. The test should be short and test one condition at a time. Consider the test below, based on a function it:
it("should be truthy", () => {
expect(hourToMins(3)).toBe(180);
expect(hourToMins(5)).toBe(300);
expect(hourToMins(null)).toBe(null);
});
The test above has multiple conditions in just a single test, which makes it ambiguous:
it returns 180 minutesit returns 300 minutesnull is passed, it returns nullInstead, each condition above should be a separate test:
it("should return 180", () => {
expect(hourToMins(3)).toBe(180);
});
it("should return 300", () => {
expect(hourToMins(5)).toBe(300);
});
it("should return null", () => {
expect(hourToMins(null)).toBe(null);
});
This makes it much clearer. Tests should also be as descriptive as possible. In the above tests, the descriptions aren’t clear; the description should be clear enough to convey what is being tested against a condition.
The React Native docs provide some best practices for testing:
Do your best to cover the following:
1. Given — some precondition.
2. When — some action executed by the function that you’re testing.
3. Then — the expected outcome.
This is also known as AAA (Arrange, Act, Assert).
So let’s make our tests more descriptive:
it("given 3 hours, hourToMins should return 180", () => {
expect(hourToMins(3)).toBe(180);
});
it("given null, hourToMins should return null", () => {
expect(hourToMins(null)).toBe(null);
});
There we go — much clearer!
There are many testing frameworks we can use to test React Native applications, including but not limited to Mocha, Jest, Jasmine, Nightmare, and WebDriver.
All of these are good choices for testing JS-based applications. Jest and Enzyme are particularly good, and both come highly recommended for testing React-based applications. But because React Native isn’t for web applications, neither Jest nor Enzyme has a React Native adapter.
Luckily, there are a few libraries that can help us out with unit testing for React Native applications, including React Test Renderer and React Native Testing Library.
Jest provides the testing environment, and React Native Testing Library provides a lightweight solution for testing React Native components. React Test Renderer provides a React renderer we can use to render React components to pure JavaScript objects without depending on the DOM or a native mobile environment.
Unit testing in React Native also covers component tests. Components are fundamental units of any React Native app; each component renders its own section of the app, and users directly interact with its output. There are two things we test in components:
From here, we will explore how to add testing functionality to our React Native app. First, let’s scaffold a new React Native project by installing the expo-cli tool globally:
yarn global add expo-cli
Next, we’ll create a new React Native project called react-native-test:
expo init react-native-test cd react-native-test yarn start # you can also use: expo start
This will start a development server. Using expo-cli to scaffold our React Native project will set up the testing environment for us automatically:
// package.json
...
"scripts": {
...
"test": "jest --watchAll"
},
"jest": {
"preset": "jest-expo"
}
...
Everything is set for us. All we have to do is run yarn test in our terminal to run the tests files. Now, we’ll open the project in VS Code, open the integrated terminal, and run yarn test. We’ll see Jest run the tests files and hang around in watch mode, watching the files so as to rerun the files if entry is made:
PASS components/__tests__/StyledText-test.js (5.018s) ✓ renders correctly (3108ms) › 1 snapshot written. Snapshot Summary › 1 snapshot written from 1 test suite. Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 1 written, 1 total Time: 5.1s Ran all test suites. Watch Usage › Press f to run only failed tests. › Press o to only run tests related to changed files. › Press p to filter by a filename regex pattern. › Press t to filter by a test name regex pattern. › Press q to quit watch mode. › Press Enter to trigger a test run.
Now we can install React Native Testing Library:
yarn add --dev @testing-library/react-native
And that’s it! Now, we’re ready to begin writing our unit tests.
Let’s say we have a Button.tsx component in the components folder:
import React from "react";
import { Text, TouchableOpacity } from "react-native";
function Button({ styles, children }) {
return (
<TouchableOpacity style={[styles.button]}>
<Text>{children}</Text>
</TouchableOpacity>
);
}
export default Button;
We can write a test to determine whether the Button component renders properly:
// __tests__
import * as React from "react";
import Button from "../Button";
import renderer from "react-test-renderer";
it(`renders correctly`, () => {
const tree = renderer.create(<Button>Login</Button>);
expect(tree).toMatchSnapshot();
});
Now, let’s write a test for the App component:
import * as React from "react";
import renderer from "react-test-renderer";
import App from "../App";
it(`renders correctly`, () => {
const tree = renderer.create(<App />).toJSON();
expect(tree.children.length).toBe(1);
});
The App renders a DOM with a single child, so this test passes.
We can mock the actual implementation with a dummy version that mimics the real one. Mocking makes tests much faster, especially those that involve internet activity. Jest allows us to add our mock implementations — let’s see how it’s done.
Let’s test a presentational component:
function TodoItem({ todo, editTodoItem, deleteTodoItem }: TodoProps) {
return (
<>
<View className="todoItem">
<View className="todoItemText">
<Text>{todo.todoText}</Text>
</View>
<View className="todoItemControls">
<TouchableHighlight
className="bg-default"
onPress={() => editTodoItem(todo)}
>
<Text>Edit</Text>
</TouchableHighlight>
<TouchableHighlight
className="bg-danger"
onPress={() => deleteTodoItem(todo)}
>
<Text>Del</Text>
</TouchableHighlight>
</View>
</View>
</>
);
}
The TodoItem component renders a todo. Note that the TodoItem component expects a todo object and the functions called when the Del and Edit buttons are clicked.
To test this, we will have to mock the function props:
it("delete and edit a todo", () => {
const mockEditTodoItemFn = jest.fn();
const mockDeleteTodoItemFn = jest.fn();
const { getByText } = render(
<TodoItem
todo={{ todoText: "go to church" }}
editTodoItem={mockEditTodoItemFn}
deleteTodoItem={mockDeleteTodoItemFn}
/>
);
fireEvent.press(getByText("Edit"));
fireEvent.press(getByText("Del"));
expect(mockEditTodoItemFn).toBeCalledWith({
1: { todoText: "go to church" },
});
expect(mockDeleteTodoItemFn).toBeCalledWith({
1: { todoText: "go to church" },
});
});
We used Jest’s fn() to create mock functions mockDeleteTodoItemFn and mockEditTodoItemFn, then we assigned them to the editTodoItem and deleteTodoItem props. The mock functions are called when a user presses the Del or Edit buttons. This ensures the buttons will work in production when the real functions are passed.
We can also mock return values:
it("delete and edit a todo", () => {
const mockEditTodoItemFn = jest.fn();
mockEditTodoItemFn.mockReturnValue({ edited: true });
const mockDeleteTodoItemFn = jest.fn();
mockDeleteTodoItemFn.mockReturnValue({ deleted: true });
const { getByText } = render(
<TodoItem
todo={{ todoText: "go to church" }}
editTodoItem={mockEditTodoItemFn}
deleteTodoItem={mockDeleteTodoItemFn}
/>
);
fireEvent.press(getByText("Edit"));
fireEvent.press(getByText("Del"));
expect(mockEditTodoItemFn.mock.results[0].value).toBe({
edited: true,
});
expect(mockDeleteTodoItemFn.mock.results[0].value).toBe({
deleted: true,
});
});
We called the .mockReturnValue(...) method on the mock functions with the values we want returned when the functions are called. This helps us test our functions on the type of value it returns.
The property .mock.results holds the array of results from the mock functions we called. From this, we were able to retrieve the returned values and test them against the expected values.
We can also mock API calls. We’ll use Axios to perform HTTP calls to our APIs. Here’s how we’d mock Axios methods like get, post, delete, and put:
...
import axios from "axios";
export default function Home() {
const [todos, setTodos] = useState([]);
useEffect(async () => {
const result = await axios.get("http://localhost:1337/todos");
setTodos(result?.data);
}, []);
const addTodo = async (todoText) => {
if (todoText && todoText.length > 0) {
const result = await axios.post("http://localhost:1337/todos", {
todoText: todoText,
});
setTodos([...todos, result?.data]);
}
};
return (
<View>
{todos.map((todo,i) => {
<Text key={i}>{todo}</Text>
})}
</View>
);
}
We have a component that loads and posts to-dos to a server. To test this component, we will need to mock the HTTP call in Axios:
jest.mock("axios");
it("component can load todos, when mounted.", () => {
const resp = {
data: [{ todoText: "go to church" }, { todoText: "go to market" }],
};
axios.get.mockResolvedValue(resp);
const { getByText } = render(<Home />);
const todos = [getByText("go to church"), getByText("go to market")];
expect(todos.length).toBe(2);
});
Here, we mocked the Axios get method. When the Home component loads the to-dos via axios.get, our mock is called and responds with a mock response. Learn more about mocking here.
We should also test for user interactions in our React Native components — interactions like pressing a button, inputting text, scrolling a list, and so on.
React Native Testing Library is a great resource for testing user interactions in React Native components. It provides methods like getByPlaceholder, getByText, and getAllByText that we can use to get the elements or text from the rendered output as nodes. We can then interact with the rendered nodes as though we were actually doing it in a browser.
Let’s look at our previous example:
it("delete and edit a todo", () => {
const mockEditTodoItemFn = jest.fn();
mockEditTodoItemFn.mockReturnValue({ edited: true });
const mockDeleteTodoItemFn = jest.fn();
mockDeleteTodoItemFn.mockReturnValue({ deleted: true });
const { getByText } = render(
<TodoItem
todo={{ todoText: "go to church" }}
editTodoItem={mockEditTodoItemFn}
deleteTodoItem={mockDeleteTodoItemFn}
/>
);
fireEvent.press(getByText("Edit"));
fireEvent.press(getByText("Del"));
expect(mockEditTodoItemFn.mock.results[0].value).toBe({
edited: true,
});
expect(mockDeleteTodoItemFn.mock.results[0].value).toBe({
deleted: true,
});
});
The TodoItem rendered two buttons with the text Del and Edit. We used getByText to access the nodes of these buttons, and fireEvent.press(...) to press the button, just as we could in an actual browser. This will fire the event handler attached to the button’s onPress event. This changes the rendered output, and we can test for what happens when buttons are clicked:
function SearchFruit() {
const [result, setResult] = useState([]);
const [searchItem, setSearchItem] = useState();
const fruits = ["orange", "apple", "guava", "lime", "lemon"];
useEffect(() => {
if (searchtiem.length > 0) {
setResult(fruits.includes(searchItem));
}
}, [searchItem]);
return (
<>
<TextInput
placeholder="Search fruits"
onChangeText={(text) => setSearchItem(text)}
/>
{result.map((fruit, i) => (
<Text key={i}>{fruit}</Text>
))}
</>
);
}
Above, we have a component that searches for fruits from an array and displays the result if found. We can write tests to ensure that the event handlers are fired when the user interacts with it and that the right results are rendered:
it("search for a fruit when the fruit name is entered/typed", () => {
const { getByPlaceholder } = render(<SearchFruit />);
fireEvent.changeText(getByPlaceholder("Search fruits"), "guava");
expect(getAllByText("guava")).toHaveLength(1);
});
Above, we’re testing that when a user types "guava", the correct result is displayed because we have the guava fruit in the fruits array. The fireEvent.changeText() method changes the text of an input box, thus firing off the onChange event handler. getAllByText gets any occurrence of the text "guava" in the array. Because we know guava is in the fruits array, then the text "guava" should be rendered.
To fire events in React Native components, we use the methods in React Native Testing Library’s fireEvent API.
pressThis fires off the press event on an element, which calls its attached handler. To fire the press event, React Native Testing Library exports the fireEvent object and calls the press() method. It receives the instance of the element to be pressed as a parameter:
const onPressMock = jest.fn();
const { getByText } = render(
<View>
<TouchableOpacity onPress={onPressMock}>
<Text>Press Me</Text>
</TouchableOpacity>
</View>
);
fireEvent.press(getByText("Press Me"));
expect(onPressMock).toHaveBeenCalled();
We set a press event in the TouchableOpacity element, with a mocked function handler attached to the event. We used the fireEvent.press() to “press” the element, so calling the mocked handler. We expect this will call the mocked handler.
changeTextThis event inputs text data in an element and fires the changeText event on the element, which calls the element’s handler:
const onChangeTextMock = jest.fn();
const { getByPlaceholder } = render(
<View>
<TextInput placeholder="Search fruits" onChangeText={onChangeTextMock} />
</View>
);
fireEvent.changeText(getByPlaceholder("Search fruits"), "guava");
expect(onChangeTextMock).toHaveBeenCalled();
We set up a mock handler in the onChangeText event of the TextInput box. We fire off the event using fireEvent.changeText(...). Now, we expect that the mocked handler is called when fireEvent.changeText(...) is called on the TextInput box.
More on events in React Native Testing Library can be found here.
Of course, there is far more to we could say about unit testing in React Native, but this should get you up and running.
We have seen how powerful Jest can be as a testing framework in React-based applications, particularly in React Native. We also saw how useful React Native Testing Library is, with its plethora of query and event methods.
If you have any questions regarding this topic, feel free to leave a comment in the comment section below!

LogRocket's Galileo AI watches sessions for you and and surfaces the technical and usability issues holding back 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.

line-clamp to trim lines of textMaster the CSS line-clamp property. Learn how to truncate text lines, ensure cross-browser compatibility, and avoid hidden UX pitfalls when designing modern web layouts.

Discover seven custom React Hooks that will simplify your web development process and make you a faster, better, more efficient developer.

Promise.all still relevant in 2025?In 2025, async JavaScript looks very different. With tools like Promise.any, Promise.allSettled, and Array.fromAsync, many developers wonder if Promise.all is still worth it. The short answer is yes — but only if you know when and why to use it.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the October 29th issue.
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 now