Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

@suite-native/test-utils

This package is wrapper around @testing-library/react-native that provides some custom utilities for testing React Native components in the Suite Native project. It also reexports everything from @testing-library/react-native, so you should use it as a drop-in replacement.

Testing components

flowchart TD
    A{{Do you need redux store?}} -- Yes --> B[Use 'renderWithStoreProvider']
    A -- No --> C{{Do you need any other provider? Such as theme, formatters, etc.}}
    C -- Yes --> D[Use 'renderWithBasicProvider']
    C -- No --> E{{Are you sure?}}
    E -- Yes --> F[Use 'render']
    E -- No --> D

Using render

render function is just re-exported from @testing-library/react-native. For more information, please refer to the official documentation.

You most probably do not want to use this function directly.

Using renderWithBasicProvider

renderWithBasicProvider is a custom utility that provides basic context providers. Namely:

  • IntlProvider: Simplified variant for tests. Always uses en-US locale.
  • StylesProvider: With colorVariant set to standard.
  • NavigationContainer
  • NativeServicesProvider: With extraDependenciesNativeMock.services.
  • FormatterProvider: With locale set to en and baseCurrency set to USD.
  • BottomSheetModalProvider

Usage example

Basic usage

import { renderWithBasicProvider, userEvent } from '@suite-native/test-utils';

describe('Counter', () => {
    it('should start with 0 value', () => {
        const { getByLabelText } = renderWithBasicProvider(<Counter />);

        expect(getByLabelText('Counter value')).toHaveTextContent('0');
    });

    it('should increment value on button press', async () => {
        const { getByLabelText, getByText } = renderWithBasicProvider(<Counter />);

        await userEvent.press(getByText('+'));

        expect(getByLabelText('Counter value')).toHaveTextContent('1');
    });
});

Using renderWithStoreProvider

renderWithStoreProvider is a custom utility that provides all the context providers from renderWithBasicProvider plus:

  • Redux store provider
  • Formatters config is now loaded from store instead of being hardcoded.
  • Allows to specify custom store or preloaded state.

Usage with preloaded state

Use when you do not need to access the store directly, but you want to set some initial state for your component. For example, you want to test how your component renders with some specific value from the store.

import {
    type PreloadedState,
    initStore,
    renderWithStoreProvider,
    userEvent,
} from '@suite-native/test-utils';

describe('Counter', () => {
    const renderCounter = (preloadedState: PreloadedState) =>
        renderWithStoreProvider(<Counter />, { preloadedState });

    it('should render correct value on 1st render', () => {
        const preloadedState: PreloadedState = {
            counter: { value: 1 },
        };
        const { getByLabelText } = renderCounter(preloadedState);

        expect(getByLabelText('Counter value')).toHaveTextContent('1');
    });

    it('should increment value on button press', async () => {
        const preloadedState: PreloadedState = {
            counter: { value: 1 },
        };
        const { getByLabelText, getByText } = renderCounter(preloadedState);

        await userEvent.press(getByText('+'));

        expect(getByLabelText('Counter value')).toHaveTextContent('2');
    });
});

Usage with custom store

Use when you need to access the store directly in your test. For example, you want to check if some action was dispatched or if the state was updated correctly. You have also possibility to dispatch an action by yourself.

import {
    type TestStore,
    act,
    initStore,
    renderWithStoreProvider,
    userEvent,
} from '@suite-native/test-utils';

describe('Counter', () => {
    let store: TestStore;

    beforeEach(() => {
        ({ store } = initStore({ counter: { value: 0 } }));
    });

    const renderCounter = () => renderWithStoreProvider(<Counter />, { store });

    it('should react to state changes', () => {
        const { getByLabelText } = renderCounter();

        act(() => {
            store.dispatch({ type: 'counter/increment' });
        });

        expect(getByLabelText('Counter value')).toHaveTextContent('1');
    });

    it('should increment value on button press', async () => {
        const { getByLabelText, getByText } = renderCounter();

        await userEvent.press(getByText('+'));

        expect(getByLabelText('Counter value')).toHaveTextContent('1');
        expect(store.getState().counter.value).toBe(1);
        expect(store.getActions()).toContainEqual({ type: 'counter/increment' });
    });
});

Injecting custom providers

To inject custom provider, you can use wrapper option of either render, renderWithBasicProvider or renderWithStoreProvider.

const renderCounterA = () =>
    renderWithStoreProvider(<Counter />, { store, wrapper: MyCustomProvider });

const renderCounterB = () =>
    renderWithBasicProvider(<Counter />, {
        wrapper: ({ children }) => (
            <MyCustomProvider someProp={someValue}>{children}</MyCustomProvider>
        ),
    });

Testing hooks

flowchart TD
    A{{Do you need redux store?}} -- Yes --> B[Use 'renderHookWithStoreProvider']
    A -- No --> C{{Do you need any other provider? Such as theme, formatters, etc.}}
    C -- Yes --> D[Use 'renderHookWithBasicProvider']
    C -- No --> F[Use 'renderHook']

Using renderHook

renderHook function is just re-exported from @testing-library/react-native. For more information, please refer to the official documentation.

You should use it when your hook does not depend on any context providers.

Using renderHookWithBasicProvider

renderHookWithBasicProvider is a custom utility that provides the same context providers as renderWithBasicProvider. For more information, please refer to the section about renderWithBasicProvider.

Usage example

import { act, renderHookWithBasicProvider } from '@suite-native/test-utils';

describe('useCounter', () => {
    const renderUseCounter = () => renderHookWithBasicProvider(() => useCounter());

    it('should initialize with count 0', () => {
        const { result } = renderUseCounter();

        expect(result.current.count).toBe(0);
    });

    describe('increment', () => {
        it('should increment count by 1', () => {
            const { result } = renderUseCounter();

            act(() => {
                result.current.increment();
            });

            expect(result.current.count).toBe(1);
        });
    });
});

Using renderHookWithStoreProvider

renderHookWithStoreProvider is a custom utility that provides the same context providers as renderWithStoreProvider. For more information, please refer to the section about renderWithStoreProvider.

Usage with preloaded state

import {
    type PreloadedState,
    act,
    initStore,
    renderHookWithStoreProvider,
} from '@suite-native/test-utils';

describe('useCounter', () => {
    const renderUseCounter = (store: PreloadedState) =>
        renderHookWithStoreProvider(() => useCounter(), { store });

    it('should initialize with count from store', () => {
        const { result } = renderUseCounter({
            counter: { value: 5 },
        });

        expect(result.current.count).toBe(5);
    });
});

Usage example with store access

import { act, initStore, renderHookWithStoreProvider } from '@suite-native/test-utils';

describe('useCounter', () => {
    const renderUseCounter = (store: TestStore) =>
        renderHookWithStoreProvider(() => useCounter(), { store });

    it('should increment count and update store', () => {
        const store = initStore({ counter: { value: 0 } });
        const { result } = renderUseCounter(store);

        act(() => {
            result.current.increment();
        });

        expect(result.current.count).toBe(1);
        expect(store.getState().counter.value).toBe(1);
        expect(store.getActions()).toContainEqual({ type: 'counter/increment' });
    });
});

Injecting custom providers

To inject custom provider, you can use wrapper option of either renderHook, renderHookWithBasicProvider or renderHookWithStoreProvider.

const renderUseCounterA = () =>
    renderHookWithStoreProvider(() => useCounter(), { store, wrapper: MyCustomProvider });

const renderUseCounterB = () =>
    renderHookWithBasicProvider(() => useCounter(), {
        wrapper: ({ children }) => (
            <MyCustomProvider someProp={someValue}>{children}</MyCustomProvider>
        ),
    });