React Native Testing Library

repository·main·Indexed 25 days ago

https://github.com/callstack/react-native-testing-library

Developer-friendly utilities for testing React Native components that encourage testing user behavior over implementation details. Features include the screen object for queries, userEvent for simulating interactions, and async utilities like findBy* and waitFor. Version 14.0.1 introduces asynchronous APIs for render(), act(), renderHook(), and fireEvent(), and drops support for React 18.

Tokens
82.7K
Snippets
239
Records
459
Agent score
82%

What's inside @testing-library/react-native

  1. Introduction to React Native Testing Library

    main

    React Native Testing Library (RNTL) is a lightweight utility for testing React Native components. It provides functions built on top of react-test-renderer to encourage testing practices that focus on user behavior rather than implementation details.

    Guiding Principle:

    The more your tests resemble how your software is used, the more confidence they can give you.

    Key Characteristics:

    • Inspired by React Testing Library.
    • Optimized for use with Jest, but compatible with other test runners.
    • Designed to make tests resilient to refactors by avoiding implementation details.
  2. Overview of React Native Testing Library APIs

    main

    React Native Testing Library provides a suite of APIs to test React Native components by simulating user interactions and inspecting the rendered UI. The core API surface includes:

    • Rendering: Use render to mount components for testing.
    • Inspection: Use the screen object to access the rendered UI via Queries (finding components by role, text, test IDs, etc.), Lifecycle methods (rerender, unmount), and Helpers (debug, toJSON, root).
    • Validation: Use Jest matchers to assert the state of your UI.
    • Interaction:
      • User Event: Simulate realistic user interactions (e.g., press, type).
      • Fire Event: Simulate component events in a simplified manner.
    • Hooks: Use renderHook to test custom hooks.
    • Asynchronous Utilities: Use findBy* queries, waitFor, and waitForElementToBeRemoved for async testing.
    • Configuration: Manage library settings with configure and resetToDefaults.
    • Accessibility: Test accessibility features like accessible name and isHiddenFromAccessibility.
    • Utilities: Access within, act, and cleanup for advanced testing scenarios.
  3. Introduction to React Native Testing Library (RNTL)

    main

    React Native Testing Library (RNTL) is a testing solution for React Native components designed to encourage best practices by focusing on user behavior rather than implementation details. It provides a React Native runtime simulation built on top of test-renderer.

    Key principles:

    • Tests should resemble how software is used to provide maximum confidence.
    • Tests should avoid implementation details to remain maintainable during refactors.
    • While tested primarily with Jest, it is compatible with other test runners.
  4. Understand the React Test Renderer environment

    main

    React Native Testing Library (RNTL) does not use the actual React Native renderer (which requires a mobile OS like iOS or Android). Instead, it uses React Test Renderer, which renders components to pure JavaScript objects in a Node.js environment.

    Benefits

    • Tests can run on CI (Linux, etc.) without a mobile device or emulator.
    • Faster test execution.
    • Lightweight runtime environment.

    Limitations

    • No native code execution: Tests do not execute actual native code.
    • No native view state: Tests are unaware of native-managed states like focus or unmanaged text boxes.
    • No native hierarchy: Assertions do not operate on the actual native view hierarchy.
    • Simulated behavior: Runtime behaviors are simulated and may not perfectly match native behavior.
  5. Understand the React Native Testing Library environment

    main

    React Native Testing Library (RNTL) does not use the actual React Native renderer. Instead, it uses Test Renderer, which renders components to pure JavaScript objects in a Node.js environment (e.g., using Jest).

    Key Characteristics

    • Execution Environment: Runs in Node.js without access to mobile OS (iOS/Android).
    • Benefits: Faster execution, runs on most CI environments (Linux) without emulators, and has a light runtime.
    • Limitations:
      • Does not execute native code.
      • Is unaware of native view state (e.g., focus, unmanaged text boxes).
      • Assertions do not operate on the actual native view hierarchy.
      • Runtime behaviors are simulated and may not perfectly match native behavior.
  6. Understand the testing environment limitations

    main

    React Native Testing Library (RNTL) does not provide a full React Native runtime. It simulates the JavaScript part of the runtime using react-test-renderer rather than running on a physical device or emulator.

    Capabilities:

    • Test most logic of regular React Native apps.
    • Run tests on any OS supported by Jest (e.g., CI environments).
    • Use fewer resources than full runtime simulation.
    • Use Jest fake timers.

    Limitations:

    • You cannot test native features (platform APIs that require an actual iOS/Android environment).
    • JavaScript feature simulation may not be 100% perfect.

    To improve simulation accuracy, use User Event interactions instead of the basic Fire Event API.

  7. Understand the React Native Testing Library rendering environment

    main

    React Native Testing Library (RNTL) does not use the actual React Native renderer, as that requires a mobile OS (iOS/Android) and a device or simulator. Instead, it uses React Test Renderer, which renders components to pure JavaScript objects in a Node.js environment.

    Benefits

    • Tests run on most CI environments (e.g., Linux) without mobile devices or emulators.
    • Faster test execution.
    • Lightweight runtime environment.

    Limitations

    • Tests do not execute native code.
    • Tests are unaware of native view states (e.g., focus, unmanaged text boxes).
    • Assertions do not operate on the actual native view hierarchy.
    • Runtime behaviors are simulated and may not perfectly match native behavior.
  8. Handle asynchronous component updates in tests

    main

    When a component performs asynchronous operations (like setTimeout or network calls) that trigger state updates, you have three primary ways to handle them in RNTL:

    1. Using Jest Fake Timers

    Wrap the timer advancement in a synchronous act() call. This is often the cleanest way to test time-dependent logic.

    2. Using waitFor

    Use the waitFor utility to poll until the expected state is reached. waitFor internally uses asynchronous act().

    3. Using findBy* queries

    Use findBy queries (e.g., findByText), which are asynchronous versions of getBy queries. These internally call waitFor and are the most idiomatic way to wait for an element to appear after an async update.

    // Solution 1: Fake Timers
    import { render, act } from '@testing-library/react-native';
    
    test('render with fake timers', () => {
      jest.useFakeTimers();
      render(<TestAsyncComponent />);
    
      act(() => {
        jest.runAllTimers();
      });
      expect(screen.getByText('Count 1')).toBeOnTheScreen();
    });
    
    // Solution 2: waitFor
    import { render, screen, waitFor } from '@testing-library/react-native';
    
    test('render with real timers - waitFor', async () => {
      render(<TestAsyncComponent />);
    
      await waitFor(() => screen.getByText('Count 1'));
      expect(screen.getByText('Count 1')).toBeOnTheScreen();
    });
    
    // Solution 3: findBy (Recommended)
    import { render, screen } from '@testing-library/react-native';
    
    test('render with real timers - findBy', async () => {
      render(<TestAsyncComponent />);
    
      expect(await screen.findByText('Count 1')).toBeOnTheScreen();
    });
  9. Migrate to React Native Testing Library v14

    main

    To migrate from RNTL v13.x to v14.x, you must update your dependencies and refactor your test code to handle asynchronous APIs. RNTL v14 requires React 19.0.0+ and React Native 0.78+. It also requires Node.js ^22.13.0 || >=24.

    Note for React 18 Users: If your project requires React 18 support, do not upgrade to v14; continue using RNTL v13.x.

  10. Await async functions in RNTL v14

    main

    In RNTL v14, several core functions are asynchronous. You must await them to ensure state updates and effects have completed before running assertions. Failing to do so can lead to intermittent test failures.

    // GOOD: await render (v14)
    await render(<Component />);
    
    // GOOD: await fireEvent (v14)
    await fireEvent.press(screen.getByRole('button'));
    
    // GOOD: await act (v14)
    await act(() => {
      result.current.increment();
    });
  11. Access queries using the `screen` object

    main

    Use the screen object exported by @testing-library/react-native to access query methods. The screen object contains all available query methods bound to the most recently rendered UI, allowing you to avoid destructuring the render result.

    import { render, screen } from '@testing-library/react-native';
    
    test('accessing queries using "screen" object', async () => {
      await render(...);
    
      screen.getByRole("button", { name: "Start" });
    })
  12. Use Async APIs in RNTL v14

    main

    RNTL v14 uses React 19's async rendering model. The following core functions are now async by default and must be awaited:

    • render(): returns Promise<RenderResult>
    • rerender() and unmount(): return Promise<void>
    • renderHook(): returns Promise<RenderHookResult>
    • fireEvent() and helpers (press, changeText, scroll): return Promise<void>
    • act(): always returns Promise<T>

    Note for v13 users: If you were using the legacy async versions (e.g., renderAsync, fireEventAsync), rename them to their standard counterparts (render, fireEvent) as the async versions have been removed.

    Example: Async render and fireEvent

    import { render, screen, fireEvent } from '@testing-library/react-native';
    
    it('should press button', async () => {
      await render(<MyComponent />);
      await fireEvent.press(screen.getByText('Press me'));
      expect(onPress).toHaveBeenCalled();
    });

    Example: Async act

    Even if your callback is synchronous, you must await the result of act():

    import { act } from '@testing-library/react-native';
    
    await act(() => {
      setState('new value');
    });
    import { render, screen, fireEvent } from '@testing-library/react-native';
    
    it('should press button', async () => {
      await render(<MyComponent />);
      await fireEvent.press(screen.getByText('Press me'));
      expect(onPress).toHaveBeenCalled();
    });