# @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 ```mermaid 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](https://testing-library.com/docs/react-native-testing-library/intro/). 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 ```tsx import { renderWithBasicProvider, userEvent } from '@suite-native/test-utils'; describe('Counter', () => { it('should start with 0 value', () => { const { getByLabelText } = renderWithBasicProvider(); expect(getByLabelText('Counter value')).toHaveTextContent('0'); }); it('should increment value on button press', async () => { const { getByLabelText, getByText } = renderWithBasicProvider(); 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. ```tsx import { type PreloadedState, initStore, renderWithStoreProvider, userEvent, } from '@suite-native/test-utils'; describe('Counter', () => { const renderCounter = (preloadedState: PreloadedState) => renderWithStoreProvider(, { 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. ```tsx 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(, { 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`. ```tsx const renderCounterA = () => renderWithStoreProvider(, { store, wrapper: MyCustomProvider }); const renderCounterB = () => renderWithBasicProvider(, { wrapper: ({ children }) => ( {children} ), }); ``` ## Testing hooks ```mermaid 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](https://testing-library.com/docs/react-native-testing-library/intro/). 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 ```ts 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 ```ts 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 ```ts 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`. ```tsx const renderUseCounterA = () => renderHookWithStoreProvider(() => useCounter(), { store, wrapper: MyCustomProvider }); const renderUseCounterB = () => renderHookWithBasicProvider(() => useCounter(), { wrapper: ({ children }) => ( {children} ), }); ```