Writing Unit Tests in Jest for React Native Components

Table of Contents
Big thanks to our contributors those make our blogs possible.

Our growing community of contributors bring their unique insights from around the world to power our blog. 

Introduction

Unit testing React Native components is essential for building scalable, maintainable mobile apps. Writing comprehensive unit tests helps catch bugs early, reduces regressions, and boosts developer confidence when refactoring. Jest—the default test runner for React Native—offers zero‑config setup, snapshot testing, powerful mocking capabilities, and coverage reporting.

In this guide, you’ll learn:

  1. Jest Setup: Installing and configuring Jest for React Native
  2. Rendering & Queries: Using React Testing Library to interact with components
  3. Snapshot Testing: When and how to capture UI snapshots
  4. Stateful & Context Tests: Testing components with hooks and React Context
  5. Mocking Native Modules: Stubbing AsyncStorage, NetInfo, and other native APIs
  6. Async Logic & Timers: Managing promises and fake timers for reliable tests
  7. Test Organization & Coverage: Structuring tests, setting coverage thresholds
  8. CI Integration: Running tests and enforcing coverage in GitHub Actions
  9. Best Practices: Flake mitigation, naming conventions, and maintenance

1. Setting Up Jest in React Native

StepCommand / Config Snippet
Install Dependenciesnpm install --save-dev jest @testing-library/react-native @testing-library/jest-native
Jest PresetIn package.json under "jest":
jsonCopyEdit"preset": "react-native",
"setupFilesAfterEnv": ["@testing-library/jest-native/extend-expect"],
"transformIgnorePatterns": ["node_modules/(?!(react-native|my-custom-module)/)"]
```                                                  |
| **Test Script**            | In `package.json` scripts:  
```json
"test": "jest --coverage"
```                                                       |

- **preset**: loads `react-native/jest-preset` for transforming RN code.  
- **setupFilesAfterEnv**: adds custom matchers like `toHaveTextContent`.  
- **transformIgnorePatterns**: ensures third‑party modules are properly transformed.

---

## 2. Rendering Components & Querying

React Testing Library (RTL) encourages testing from the user’s perspective:

```jsx
// Button.js
import React from 'react';
import { TouchableOpacity, Text } from 'react-native';

export default function Button({ label, onPress }) {
  return (
    <TouchableOpacity accessibilityRole="button" onPress={onPress}>
      <Text>{label}</Text>
    </TouchableOpacity>
  );
}
jsxCopyEdit// Button.test.js
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import Button from './Button';

describe('Button Component', () => {
  it('renders the correct label', () => {
    const { getByText } = render(<Button label="Click Me" />);
    expect(getByText('Click Me')).toBeTruthy();
  });

  it('invokes callback on press', () => {
    const onPress = jest.fn();
    const { getByRole } = render(<Button label="Tap" onPress={onPress} />);
    fireEvent.press(getByRole('button'));
    expect(onPress).toHaveBeenCalledTimes(1);
  });
});
  • render: mounts the component into a lightweight virtual tree.
  • getByText / getByRole: queries by visible text or accessibility role.
  • fireEvent.press: simulates a tap event on mobile.

3. Snapshot Testing Wisely

Snapshots capture serialized component trees for regression detection:

jsxCopyEditimport React from 'react';
import renderer from 'react-test-renderer';
import Button from './Button';

it('matches the UI snapshot', () => {
  const tree = renderer.create(<Button label="Snapshot" />).toJSON();
  expect(tree).toMatchSnapshot();
});

When to Use Snapshots

  • Simple, Presentational Components: Static styles, no dynamic data.
  • Avoid Deep Trees: Snapshots of large component hierarchies lead to brittle tests.
  • Review Updates: Update snapshots (jest -u) only after verifying UI intent.

4. Testing Stateful & Context‑Aware Components

4.1 Testing Hooks and Local State

jsxCopyEdit// Counter.js
import React, { useState } from 'react';
import { View, Text, Button as RNButton } from 'react-native';

export default function Counter() {
  const [count, setCount] = useState(0);
  return (
    <View>
      <Text testID="count">{count}</Text>
      <RNButton title="Increment" onPress={() => setCount(c => c + 1)} />
    </View>
  );
}
jsxCopyEdit// Counter.test.js
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import Counter from './Counter';

it('increments count on button press', () => {
  const { getByTestId, getByText } = render(<Counter />);
  const count = getByTestId('count');
  expect(count.props.children).toBe(0);
  fireEvent.press(getByText('Increment'));
  expect(count.props.children).toBe(1);
});

4.2 Testing Components with React Context

jsxCopyEdit// ThemeContext.js
import React from 'react';
export const ThemeContext = React.createContext('light');

// ThemedText.js
import React from 'react';
import { Text } from 'react-native';
import { ThemeContext } from './ThemeContext';

export default function ThemedText({ children }) {
  const theme = React.useContext(ThemeContext);
  const color = theme === 'dark' ? 'white' : 'black';
  return <Text style={{ color }}>{children}</Text>;
}
jsxCopyEdit// ThemedText.test.js
import React from 'react';
import { render } from '@testing-library/react-native';
import { ThemeContext } from './ThemeContext';
import ThemedText from './ThemedText';

it('renders dark theme correctly', () => {
  const { getByText } = render(
    <ThemeContext.Provider value="dark">
      <ThemedText>Hello</ThemedText>
    </ThemeContext.Provider>
  );
  const node = getByText('Hello');
  expect(node.props.style.color).toBe('white');
});

5. Mocking Native Modules & Async Logic

ModuleMock Strategy
AsyncStorageUse @react-native-async-storage/async-storage/jest/async-storage-mock
NetInfoPlace a manual mock in __mocks__/@react-native-community/netinfo.js
LocalizationStub i18n functions to return keys or defaults

Setup in jestSetup.js:

jsCopyEditimport mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock';
jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage);

6. Handling Timers & Promises

Leverage Jest’s fake timers for deterministic async tests:

jsxCopyEditimport React from 'react';
import { render, waitFor } from '@testing-library/react-native';
import DataFetcher from './DataFetcher';

jest.useFakeTimers();

it('displays data after delay', async () => {
  const { getByText, queryByText } = render(<DataFetcher />);
  // initial loading state
  expect(getByText('Loading...')).toBeTruthy();

  // fast-forward pending timers
  jest.runAllTimers();

  await waitFor(() => {
    expect(queryByText('Loading...')).toBeNull();
    expect(getByText('Data loaded')).toBeTruthy();
  });
});
  • jest.useFakeTimers(): intercepts timer APIs.
  • jest.runAllTimers(): advances all simulated time.
  • waitFor(): wraps assertions that update asynchronously.

7. Organizing Tests & Coverage

7.1 Directory Structure

cssCopyEditsrc/
  components/
    Button.js
    Button.test.js
    __snapshots__/
  screens/
    HomeScreen.js
    HomeScreen.test.js
  context/
    ThemeContext.js
  __tests__/
    utils.test.js
  • Co‑locate tests with the code they cover.
  • Use a global __tests__ folder for shared utilities or high‑level integration tests.

7.2 Coverage Thresholds

Add to package.json under "jest":

jsonCopyEdit"collectCoverage": true,
"coverageThreshold": {
  "global": {
    "branches": 80,
    "functions": 85,
    "lines": 90,
    "statements": 90
  }
}

CI pipelines will fail if coverage falls below these targets, enforcing quality gates.

8. Integrating into CI/CD

GitHub Actions Workflow

yamlCopyEditname: React Native Unit Tests

on: [push, pull_request]

jobs:
test:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3

- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 16

- name: Install dependencies
run: npm ci

- name: Run Jest tests with coverage
run: npm test

- name: Upload coverage report
uses: actions/upload-artifact@v3
with:
name: coverage-report
path: coverage/
  • macos-latest ensures native mocks load and iOS modules resolve.
  • Coverage artifact lets teams review reports after the run.

9. Best Practices & Maintenance

PracticeBenefit
Stable Test IDsUse testID props to avoid brittle text-based selectors
Avoid Over‑MockingKeep tests realistic by mocking only external dependencies
Minimal SnapshotsKeep snapshot tests focused on small presentational units
Test IsolationReset component state between tests; use cleanup() for RTL
Fail‑FastRun linting and unit tests before integration or deployment
Periodic Test ReviewPrune or update outdated tests quarterly to reduce flakiness

Conclusion

Unit testing React Native components with Jest and React Testing Library drastically improves code reliability, reduces regression costs, and speeds up development cycles. By setting up Jest correctly, writing tests that mirror real user interactions, using snapshots judiciously, mocking native modules, and integrating into CI/CD, you establish a robust safety net for your app. Start by writing tests for your simplest components today and gradually expand coverage to critical parts of your application—each passing test strengthens your foundation and empowers faster, safer releases.

Let's connect on TikTok

Join our newsletter to stay updated

Sydney Based Software Solutions Professional who is crafting exceptional systems and applications to solve a diverse range of problems for the past 10 years.

Share the Post

Related Posts