diff --git a/versioned_docs/version-7.x/testing.md b/versioned_docs/version-7.x/testing.md
index 3c7b0a298f..4af9141ec9 100644
--- a/versioned_docs/version-7.x/testing.md
+++ b/versioned_docs/version-7.x/testing.md
@@ -85,18 +85,129 @@ We will go through some real-world case test code examples. Each code example co
Navigate to another screen by button press.
+Navigator:
+
```js
+import { useNavigation } from '@react-navigation/native';
+import { createStackNavigator } from '@react-navigation/stack';
+import { Button, Text, View } from 'react-native';
+
+const HomeScreen = () => {
+ const navigation = useNavigation();
+
+ return (
+
+ Home screen
+
+ );
+};
+
+const SettingsScreen = () => {
+ return (
+
+ Settings screen
+
+ );
+};
+export const StackNavigator = createStackNavigator({
+ screens: {
+ Home: HomeScreen,
+ Settings: SettingsScreen,
+ },
+});
```
```js
+import { createStackNavigator } from '@react-navigation/stack';
+import { Button, Text, View } from 'react-native';
+
+const HomeScreen = ({ navigation }) => {
+ return (
+
+ Home screen
+
+ );
+};
+
+const SettingsScreen = () => {
+ return (
+
+ Settings screen
+
+ );
+};
+
+export const StackNavigator = () => {
+ const Stack = createStackNavigator();
+ return (
+
+
+
+
+ );
+};
+```
+
+
+
+
+Test:
+
+
+
+
+```js
+import { expect, test } from '@jest/globals';
+import { createStaticNavigation } from '@react-navigation/native';
+import { fireEvent, render, screen } from '@testing-library/react-native';
+
+import { StackNavigator } from './StackNavigator';
+
+test('navigates to settings by button press', () => {
+ const StackNavigation = createStaticNavigation(StackNavigator);
+
+ render();
+
+ fireEvent.press(screen.queryByText('Go to Settings'));
+ expect(screen.queryByText('Settings screen')).toBeOnTheScreen();
+});
+```
+
+
+
+
+```js
+import { expect, test } from '@jest/globals';
+import { NavigationContainer } from '@react-navigation/native';
+import { fireEvent, render, screen } from '@testing-library/react-native';
+
+import { StackNavigator } from './StackNavigator';
+test('navigates to settings by button press', () => {
+ render(
+
+
+
+ );
+
+ fireEvent.press(screen.queryByText('Go to Settings'));
+ expect(screen.queryByText('Settings screen')).toBeOnTheScreen();
+});
```
@@ -108,6 +219,166 @@ We simulate user’s button press by `FireEvent` and call `expect` to check if r
Navigate to another tab caused by tab bar button press.
+Navigator:
+
+
+
+
+```js
+import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
+import { Text, View } from 'react-native';
+
+const HomeScreen = () => {
+ return (
+
+ Home screen
+
+ );
+};
+
+const SettingsScreen = () => {
+ return (
+
+ Settings screen
+
+ );
+};
+
+export const TabNavigator = createBottomTabNavigator({
+ screens: {
+ Home: {
+ screen: HomeScreen,
+ options: {
+ tabBarButtonTestID: 'home-tab',
+ },
+ },
+ Settings: {
+ screen: SettingsScreen,
+ options: {
+ tabBarButtonTestID: 'settings-tab',
+ },
+ },
+ },
+ screenOptions: {
+ headerShown: false,
+ },
+});
+```
+
+
+
+
+```js
+import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
+import { Text, View } from 'react-native';
+
+const HomeScreen = () => {
+ return (
+
+ Home screen
+
+ );
+};
+
+const SettingsScreen = () => {
+ return (
+
+ Settings screen
+
+ );
+};
+
+const Tab = createBottomTabNavigator();
+
+export const TabNavigator = () => {
+ return (
+
+
+
+
+ );
+};
+```
+
+
+
+
+Test:
+
+
+
+
+```js
+import { expect, jest, test } from '@jest/globals';
+import { createStaticNavigation } from '@react-navigation/native';
+import { act, fireEvent, render, screen } from '@testing-library/react-native';
+
+import { TabNavigator } from './TabNavigator';
+
+test('navigates to settings by tab bar button press', () => {
+ const TabNavigation = createStaticNavigation(TabNavigator);
+ jest.useFakeTimers();
+
+ render();
+
+ act(() => jest.runAllTimers());
+
+ const button = screen.queryByTestId('settings-tab');
+
+ // You need to pass event object to fireEvent to prevent error
+
+ fireEvent.press(button);
+
+ expect(screen.queryByText('Settings screen')).toBeOnTheScreen();
+});
+```
+
+
+
+
+```js
+import { expect, jest, test } from '@jest/globals';
+import { NavigationContainer } from '@react-navigation/native';
+import { act, fireEvent, render, screen } from '@testing-library/react-native';
+
+import { TabNavigator } from './TabNavigator';
+
+test('navigates to settings by tab bar button press', () => {
+ jest.useFakeTimers();
+
+ render(
+
+
+
+ );
+
+ act(() => jest.runAllTimers());
+
+ const button = screen.queryByTestId('settings-tab');
+
+ // You need to pass event object to fireEvent to prevent error
+ const event = {};
+ fireEvent.press(button, event);
+
+ expect(screen.queryByText('Settings screen')).toBeOnTheScreen();
+});
+```
+
+
+
+
We use `id` to query for tab buttons, simulate button press by `FireEvent` and check if rendered content is correct.
To setup tab `id` you need to add `tabBarButtonTestID` in settings tab screen `options` defined in `TabNavigator`.
@@ -120,12 +391,515 @@ Sometimes navigation animations need some time to finish and render screen's con
Always display first screen of the settings stack nested in tab after settings stack tab focus.
+Navigator:
+
+
+
+
+```js
+import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
+import { createNativeStackNavigator } from '@react-navigation/native-stack';
+import { useEffect } from 'react';
+import { Button, Text, View } from 'react-native';
+
+function HomeScreen() {
+ return (
+
+ Home screen
+
+ );
+}
+
+function SettingsScreen({ navigation }) {
+ return (
+
+ Settings screen
+
+ );
+}
+
+const DetailsScreen = ({ navigation }) => {
+ useEffect(() => {
+ const unsubscribe = navigation.getParent().addListener('tabPress', (e) => {
+ navigation.popToTop();
+ });
+ return unsubscribe;
+ }, [navigation]);
+
+ return (
+
+ Details screen
+
+ );
+};
+
+const SettingsStack = createNativeStackNavigator();
+
+function SettingsStackScreen() {
+ return (
+
+
+
+
+ );
+}
+
+const Tab = createBottomTabNavigator();
+
+export function TabNavigator() {
+ return (
+
+
+
+
+ );
+}
+```
+
+
+
+
+```js
+import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
+import { useNavigation } from '@react-navigation/native';
+import { createStackNavigator } from '@react-navigation/stack';
+import { useEffect } from 'react';
+import { Button, Text, View } from 'react-native';
+
+function HomeScreen() {
+ return (
+
+ Home screen
+
+ );
+}
+
+function SettingsScreen() {
+ const navigation = useNavigation();
+
+ return (
+
+ Settings screen
+
+ );
+}
+
+const DetailsScreen = () => {
+ const navigation = useNavigation();
+
+ useEffect(() => {
+ const unsubscribe = navigation.getParent().addListener('tabPress', (e) => {
+ navigation.popToTop();
+ });
+ return unsubscribe;
+ }, [navigation]);
+
+ return (
+
+ Details screen
+
+ );
+};
+
+const SettingsNavigator = createStackNavigator({
+ screens: {
+ Settings: SettingsScreen,
+ Details: DetailsScreen,
+ },
+});
+
+export const TabNavigator = createBottomTabNavigator({
+ screens: {
+ Home: {
+ screen: HomeScreen,
+ options: {
+ tabBarButtonTestID: 'home-tab',
+ },
+ },
+ SettingsStack: {
+ screen: SettingsNavigator,
+ options: {
+ tabBarButtonTestID: 'settings-tab',
+ },
+ },
+ },
+ screenOptions: {
+ headerShown: false,
+ },
+});
+```
+
+
+
+
+Test:
+
+
+
+
+```js
+import { expect, jest, test } from '@jest/globals';
+import { createStaticNavigation } from '@react-navigation/native';
+import { act, fireEvent, render, screen } from '@testing-library/react-native';
+
+import { TabNavigator } from './TabNavigator';
+
+test('always displays first screen in settings stack after tab bar settings button press', async () => {
+ const TabNavigation = createStaticNavigation(TabNavigator);
+ jest.useFakeTimers();
+
+ render();
+
+ act(() => jest.runAllTimers());
+
+ const homeTabButton = screen.queryByTestId('home-tab');
+ const settingsTabButton = screen.queryByTestId('settings-tab');
+
+ // You need to pass event object to fireEvent to prevent error
+ const event = {};
+
+ fireEvent.press(settingsTabButton, event);
+ expect(screen.queryByText('Settings screen')).toBeOnTheScreen();
+
+ fireEvent.press(screen.queryByText('Go to Details'), event);
+ expect(screen.queryByText('Details screen')).toBeOnTheScreen();
+
+ fireEvent.press(homeTabButton, event);
+ expect(screen.queryByText('Home screen')).toBeOnTheScreen();
+
+ fireEvent.press(settingsTabButton, event);
+ expect(screen.queryByText('Settings screen')).toBeOnTheScreen();
+});
+```
+
+
+
+
+```js
+import { expect, jest, test } from '@jest/globals';
+import { NavigationContainer } from '@react-navigation/native';
+import { act, fireEvent, render, screen } from '@testing-library/react-native';
+
+import { TabNavigator } from './TabNavigator';
+
+test('always displays first screen in settings stack after tab bar settings button press', async () => {
+ jest.useFakeTimers();
+
+ render(
+
+
+
+ );
+
+ act(() => jest.runAllTimers());
+
+ const homeTabButton = screen.queryByTestId('home-tab');
+ const settingsTabButton = screen.queryByTestId('settings-tab');
+
+ // You need to pass event object to fireEvent to prevent error
+ const event = {};
+
+ fireEvent.press(settingsTabButton, event);
+ expect(screen.queryByText('Settings screen')).toBeOnTheScreen();
+
+ fireEvent.press(screen.queryByText('Go to Details'), event);
+ expect(screen.queryByText('Details screen')).toBeOnTheScreen();
+
+ fireEvent.press(homeTabButton, event);
+ expect(screen.queryByText('Home screen')).toBeOnTheScreen();
+
+ fireEvent.press(settingsTabButton, event);
+ expect(screen.queryByText('Settings screen')).toBeOnTheScreen();
+});
+```
+
+
+
+
We query tab buttons, simulating button presses and check if rendered screens are correct.
### Example 4
Display loading state while waiting for data and then fetched nick on every profile screen focus.
+Navigator:
+
+
+
+
+```js
+import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
+import { useFocusEffect } from '@react-navigation/native';
+import { useCallback, useState } from 'react';
+import { Text, View } from 'react-native';
+
+function HomeScreen() {
+ return (
+
+ Home screen
+
+ );
+}
+
+const url = 'your_url';
+
+function ProfileScreen() {
+ const [loading, setLoading] = useState(true);
+ const [data, setData] = useState();
+ const [error, setError] = useState();
+
+ useFocusEffect(
+ useCallback(() => {
+ fetch(url)
+ .then((res) => res.json())
+ .then((res) => setData(res))
+ .catch((error) => setError(error))
+ .finally(() => setLoading(false));
+
+ return () => {
+ setData(undefined);
+ setError(undefined);
+ setLoading(true);
+ };
+ }, [])
+ );
+ return (
+
+ {loading && Loading}
+ {!loading && error && {error.message}}
+ {!loading && !error && {data.profile.nick}}
+
+ );
+}
+
+export const TabNavigator = createBottomTabNavigator({
+ screens: {
+ Home: {
+ screen: HomeScreen,
+ options: {
+ tabBarButtonTestID: 'home-tab',
+ },
+ },
+ Profile: {
+ screen: ProfileScreen,
+ options: {
+ tabBarButtonTestID: 'profile-tab',
+ },
+ },
+ },
+ screenOptions: {
+ headerShown: false,
+ },
+});
+```
+
+
+
+
+```js
+import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
+import { useFocusEffect } from '@react-navigation/native';
+import { useCallback, useState } from 'react';
+import { Text, View } from 'react-native';
+
+function HomeScreen() {
+ return (
+
+ Home screen
+
+ );
+}
+
+const url = 'your_url';
+
+function ProfileScreen() {
+ const [loading, setLoading] = useState(true);
+ const [data, setData] = useState();
+ const [error, setError] = useState();
+
+ useFocusEffect(
+ useCallback(() => {
+ fetch(url)
+ .then((res) => res.json())
+ .then((res) => setData(res))
+ .catch((error) => setError(error))
+ .finally(() => setLoading(false));
+
+ return () => {
+ setData(undefined);
+ setError(undefined);
+ setLoading(true);
+ };
+ }, [])
+ );
+ return (
+
+ {loading && Loading}
+ {!loading && error && {error.message}}
+ {!loading && !error && {data.profile.nick}}
+
+ );
+}
+
+const Tab = createBottomTabNavigator();
+
+export function TabNavigator() {
+ return (
+
+
+
+
+ );
+}
+```
+
+
+
+
+Test:
+
+
+
+
+```js
+import { expect, jest, test } from '@jest/globals';
+import { createStaticNavigation } from '@react-navigation/native';
+import { act, fireEvent, render, screen } from '@testing-library/react-native';
+
+import { TabNavigator } from './TabNavigator';
+
+async function mockedFetch() {
+ const mockResponse = {
+ profile: {
+ nick: 'CookieDough',
+ },
+ };
+ return {
+ ok: true,
+ status: 200,
+ json: async () => {
+ return mockResponse;
+ },
+ };
+}
+
+test('fetches profile data on profile screen focus', async () => {
+ const TabNavigation = createStaticNavigation(TabNavigator);
+ jest.useFakeTimers();
+
+ render();
+
+ act(() => jest.runAllTimers());
+
+ const spy = jest.spyOn(window, 'fetch').mockImplementation(mockedFetch);
+
+ const homeTabButton = screen.queryByTestId('home-tab');
+ const dogsTabButton = screen.queryByTestId('profile-tab');
+
+ // You need to pass event object to fireEvent to prevent error
+ const event = {};
+
+ fireEvent.press(dogsTabButton, event);
+
+ expect(screen.queryByText('Loading')).toBeOnTheScreen();
+ expect(spy).toHaveBeenCalled();
+ expect(await screen.findByText('CookieDough')).toBeOnTheScreen();
+
+ fireEvent.press(homeTabButton, event);
+ fireEvent.press(dogsTabButton, event);
+
+ expect(screen.queryByText('Loading')).toBeOnTheScreen();
+ expect(spy).toHaveBeenCalled();
+ expect(await screen.findByText('CookieDough')).toBeOnTheScreen();
+});
+```
+
+
+
+
+```js
+import { expect, jest, test } from '@jest/globals';
+import { NavigationContainer } from '@react-navigation/native';
+import { act, fireEvent, render, screen } from '@testing-library/react-native';
+
+import { TabNavigator } from './TabNavigator';
+
+async function mockedFetch() {
+ const mockResponse = {
+ profile: {
+ nick: 'CookieDough',
+ },
+ };
+ return {
+ ok: true,
+ status: 200,
+ json: async () => {
+ return mockResponse;
+ },
+ };
+}
+
+test('fetches profile data on profile screen focus', async () => {
+ jest.useFakeTimers();
+
+ render(
+
+
+
+ );
+
+ act(() => jest.runAllTimers());
+
+ const spy = jest.spyOn(window, 'fetch').mockImplementation(mockedFetch);
+
+ const homeTabButton = screen.queryByTestId('home-tab');
+ const dogsTabButton = screen.queryByTestId('profile-tab');
+
+ // You need to pass event object to fireEvent to prevent error
+ const event = {};
+
+ fireEvent.press(dogsTabButton, event);
+
+ expect(screen.queryByText('Loading')).toBeOnTheScreen();
+ expect(spy).toHaveBeenCalled();
+ expect(await screen.findByText('CookieDough')).toBeOnTheScreen();
+
+ fireEvent.press(homeTabButton, event);
+ fireEvent.press(dogsTabButton, event);
+
+ expect(screen.queryByText('Loading')).toBeOnTheScreen();
+ expect(spy).toHaveBeenCalled();
+ expect(await screen.findByText('CookieDough')).toBeOnTheScreen();
+});
+```
+
+
+
+
We query bottoms tabs buttons and mock fetch function using `spyOn` and `mockImplementation`. Then, we navigate to profile screen and checks if loading state displays correctly. To check if fetched data is displayed we add `await` to query - we need to wait for the fetch to finish before checking if fetched data is correct. To ensure that operation will succeed on every focus, we cause it again by navigating back to home, again to settings and check rendered content again.
It a good practice to use mocks while testing API functions. `mockedFetch` will override real implementation of fetch and enable us to make test deterministic. Thanks to `spyOnYou` you can check if mocked function was called using `toHaveBeenCalled` assertion.