docs: refactor react-navigation example with React Nav team feedback (#1253)

This commit is contained in:
Maciej Jastrzebski
2022-12-29 09:36:21 +01:00
committed by GitHub
parent 6fba252653
commit a218bbcf57
4 changed files with 26 additions and 47 deletions
+11 -4
View File
@@ -1,9 +1,16 @@
# RNTL example app for React Navigation
This example shows how to write integration tests using React Navigation without mocking it.
This example shows how to write integration tests using React Navigation without mocking it. Presented approach has been consulted with and influenced by React Navigation team.
There are two types of tests:
1. integration tests operating on whole navigators, they should use `renderNavigator` helper to render a navigator component used in the app. It is useful when you want to test a scenario that includes multiple screens.
2. single screen tests where you would pass mock `navigation` prop, built using `buildNavigationMock()` helper, and `route` prop to the screen component using regular `render` function.
## Recommended tests
There are two types of recommeded tests:
1. Tests operating on navigator level - these use `renderNavigator` helper to render a navigator component used in the app. It is useful when you want to test a scenario that includes multiple screens.
2. Tests operating on single screen level - these use regular `render` helper but require refactoring screen components into `Screen` and `ScreenContent` components. Where `Screen` receives React Navigation props and/or uses hooks like `useNavigation` while `ScreenContent` does not have a direct relation to React Navigation API but gets props from `Screen` and calls relevant callbacks to trigger navigation.
> Note that this example applies `includeHiddenElements: false` by default, so all queries will ignore elements on the hidden screens, e.g. inactive tabs or screens present in stack navigators. This option is enabled in `jest-setup.js` file, using `defaultIncludeHiddenElements: false` option to `configure` function.
## Non-recommended tests
There also exists another popular type of screen level tests, where users mock React Navigation objects like `navigation`, `route` and/or hooks like `useNavigation`, etc. We don't recommend this way of testing. **Mocking internal parts of the libraries is effectively testing implementation details, which goes against the Testing Library's [Guiding Principles](https://testing-library.com/docs/guiding-principles/)**.
+9 -7
View File
@@ -1,10 +1,14 @@
import * as React from 'react';
import { StyleSheet, View, Text, Pressable } from 'react-native';
import { useNavigation } from '@react-navigation/native';
export default function DetailsScreen({ route }) {
export default function DetailsScreen({ navigation, route }) {
const item = route.params;
return (
<DetailsScreenContent item={item} onGoBack={() => navigation.goBack()} />
);
}
export function DetailsScreenContent({ item, onGoBack }) {
return (
<View>
<Text accessibilityRole="header" style={styles.header}>
@@ -14,16 +18,14 @@ export default function DetailsScreen({ route }) {
The number you have chosen is {item.value}.
</Text>
<BackButton />
<BackButton onPress={onGoBack} />
</View>
);
}
function BackButton() {
const navigation = useNavigation();
function BackButton({ onPress }) {
return (
<Pressable accessibilityRole="button" onPress={() => navigation.goBack()}>
<Pressable accessibilityRole="button" onPress={onPress}>
<Text>Go Back</Text>
</Pressable>
);
@@ -1,35 +1,18 @@
import * as React from 'react';
import { render, screen, fireEvent } from '@testing-library/react-native';
import { useNavigation } from '@react-navigation/native';
import { buildNavigationMock } from '../test-utils';
import DetailsScreen from './DetailsScreen';
jest.mock('@react-navigation/native', () => {
const originalModule = jest.requireActual('@react-navigation/native');
return {
...originalModule,
useNavigation: jest.fn(),
};
});
let navigation;
// Reset navigation before each test
beforeEach(() => {
navigation = buildNavigationMock();
useNavigation.mockImplementation(() => navigation);
});
import { DetailsScreenContent } from './DetailsScreen';
test('Details screen contains the header and content', () => {
const params = {
const item = {
id: 100,
title: 'Item 100',
value: 100,
};
const onGoBack = jest.fn();
// Passing both navigation and route to the screen as props
render(<DetailsScreen navigation={navigation} route={{ params }} />);
render(<DetailsScreenContent item={item} onGoBack={onGoBack} />);
expect(
screen.getByRole('header', { name: 'Details for Item 100' })
@@ -38,5 +21,5 @@ test('Details screen contains the header and content', () => {
// Note: Go Back button get navigation from `useNavigation` hook
fireEvent.press(screen.getByRole('button', { name: 'Go Back' }));
expect(navigation.goBack).toHaveBeenCalledTimes(1);
expect(onGoBack).toHaveBeenCalledTimes(1);
});
-13
View File
@@ -10,16 +10,3 @@ import { render } from '@testing-library/react-native';
export function renderNavigator(ui) {
return render(<NavigationContainer>{ui}</NavigationContainer>);
}
export function buildNavigationMock() {
return {
navigate: jest.fn(),
reset: jest.fn(),
goBack: jest.fn(),
dispatch: jest.fn(),
isFocused: jest.fn(() => true),
setParams: jest.fn(),
setOptions: jest.fn(),
addListener: jest.fn(),
};
}