-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Update testing section #1374
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
AnCichocka
wants to merge
25
commits into
react-navigation:main
from
AnCichocka:update-testing-section
Closed
Update testing section #1374
Changes from 4 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
20ff1fb
add missing sections
AnCichocka abfc7b3
add more examples
AnCichocka 5861eae
clean up
AnCichocka f06232c
add
AnCichocka ad3821a
add missing static examples, react-native mock
AnCichocka 02e0b0e
update fake timers test
AnCichocka c8e9a0c
move create static navigation
AnCichocka 8f4fd6f
add preload test
AnCichocka d41d947
add comments
AnCichocka 7121450
make screen titles consistent
AnCichocka 4df7263
correct text
AnCichocka f293b0c
add screen to screen component name
AnCichocka d20e905
delete unnecessary prop
AnCichocka c7e6b6a
change tests order
AnCichocka 88a6e02
fix missing return statement
AnCichocka 9f02dd3
delete previous tests
AnCichocka 6039c03
init text
AnCichocka b18291a
add code
AnCichocka 13bfcc2
use getByRole
AnCichocka c582029
correct comment
AnCichocka 5c5ec89
fix fake timers
AnCichocka 55e6cb8
text clean up
AnCichocka 7da2155
swap url
AnCichocka 6f506b0
clean up
AnCichocka b3ff6b6
clean up
AnCichocka File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,18 +25,12 @@ If you're using `@react-navigation/stack`, you will only need to mock: | |
To add the mocks, create a file `jest/setup.js` (or any other file name of your choice) and paste the following code in it: | ||
|
||
```js | ||
// include this line for mocking react-native-gesture-handler | ||
// Include this line for mocking react-native-gesture-handler | ||
import 'react-native-gesture-handler/jestSetup'; | ||
|
||
// include this section and the NativeAnimatedHelper section for mocking react-native-reanimated | ||
// Include this section for mocking react-native-reanimated | ||
jest.mock('react-native-reanimated', () => { | ||
const Reanimated = require('react-native-reanimated/mock'); | ||
|
||
// The mock for `call` immediately calls the callback which is incorrect | ||
// So we override it with a no-op | ||
Reanimated.default.call = () => {}; | ||
|
||
return Reanimated; | ||
require('react-native-reanimated/mock'); | ||
}); | ||
|
||
// Silence the warning: Animated: `useNativeDriver` is not supported because the native animated module is missing | ||
|
@@ -54,53 +48,257 @@ Then we need to use this setup file in our jest config. You can add it under `se | |
|
||
Make sure that the path to the file in `setupFiles` is correct. Jest will run these files before running your tests, so it's the best place to put your global mocks. | ||
|
||
If your configuration works correctly, you can skip this section, but in some unusual cases you will need to mock `react-native-screens` as well. To do so add the following code in `jest/setup.js` file: | ||
|
||
```js | ||
// Include this section form mocking react-native-screens | ||
jest.mock('react-native-screens', () => { | ||
// Require actual module instead of a mock | ||
let screens = jest.requireActual('react-native-screens'); | ||
|
||
// All exports in react-native-screens are getters | ||
// We cannot use spread for cloning as it will call the getters | ||
// So we need to clone it with Object.create | ||
screens = Object.create( | ||
Object.getPrototypeOf(screens), | ||
Object.getOwnPropertyDescriptors(screens) | ||
); | ||
|
||
return screens; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are missing here example of actual mock of some component from |
||
}); | ||
``` | ||
|
||
If you're not using Jest, then you'll need to mock these modules according to the test framework you are using. | ||
|
||
## Writing tests | ||
|
||
We recommend using [React Native Testing Library](https://callstack.github.io/react-native-testing-library/) along with [`jest-native`](https://github.com/testing-library/jest-native) to write your tests. | ||
|
||
Example: | ||
We are going to write example tests illustrating the difference between `navigate` and `push` functions using Root Navigator defined below: | ||
|
||
<Tabs groupId="example" queryString="example"> | ||
<TabItem value="static" label="Static" default> | ||
|
||
```js | ||
|
||
``` | ||
|
||
</TabItem> | ||
<TabItem value="dynamic" label="Dynamic"> | ||
|
||
```js | ||
import { Button, Text, View } from 'react-native'; | ||
import { createNativeStackNavigator } from '@react-navigation/native-stack'; | ||
|
||
const Profile = ({ navigation }) => { | ||
return ( | ||
<View> | ||
<Text>Profile</Text> | ||
<Button | ||
onPress={() => navigation.navigate('Settings')} | ||
title="Navigate to Settings" | ||
/> | ||
<Button | ||
onPress={() => navigation.push('Settings')} | ||
title="Push Settings" | ||
/> | ||
<Button | ||
onPress={() => setTimeout(() => navigation.navigate('Settings'), 10000)} | ||
title="Navigate to Settings with 10000 ms delay" | ||
/> | ||
</View> | ||
); | ||
}; | ||
|
||
const Settings = () => { | ||
return ( | ||
<View> | ||
<Text>Settings</Text> | ||
</View> | ||
); | ||
}; | ||
|
||
export const RootNavigator = () => { | ||
const Stack = createNativeStackNavigator(); | ||
return ( | ||
<Stack.Navigator> | ||
<Stack.Screen name="Profile" component={Profile} /> | ||
<Stack.Screen name="Settings" component={Settings} /> | ||
</Stack.Navigator> | ||
); | ||
}; | ||
``` | ||
|
||
</TabItem> | ||
</Tabs> | ||
|
||
`navigate` function test example: | ||
|
||
<Tabs groupId="config" queryString="config"> | ||
<Tabs groupId="example" queryString="example"> | ||
<TabItem value="static" label="Static" default> | ||
|
||
```js name='Testing with jest' | ||
import * as React from 'react'; | ||
import { screen, render, fireEvent } from '@testing-library/react-native'; | ||
import { createStaticNavigation } from '@react-navigation/native'; | ||
```js | ||
|
||
``` | ||
|
||
</TabItem> | ||
<TabItem value="dynamic" label="Dynamic"> | ||
|
||
```js | ||
import { expect, test } from '@jest/globals'; | ||
import { fireEvent, render, screen } from '@testing-library/react-native'; | ||
import { | ||
createNavigationContainerRef, | ||
NavigationContainer, | ||
} from '@react-navigation/native'; | ||
import { RootNavigator } from './RootNavigator'; | ||
|
||
const Navigation = createStaticNavigation(RootNavigator); | ||
test('navigates to settings screen twice', () => { | ||
const navigation = createNavigationContainerRef(); | ||
render( | ||
<NavigationContainer ref={navigation}> | ||
<RootNavigator /> | ||
</NavigationContainer> | ||
); | ||
|
||
const button = screen.getByText('Navigate to Settings'); | ||
fireEvent.press(button); | ||
fireEvent.press(button); | ||
|
||
expect(navigation.getState().routes.map((route) => route.name)).toStrictEqual( | ||
['Profile', 'Settings'] | ||
); | ||
expect(screen.queryByText('Profile')).not.toBeOnTheScreen(); | ||
expect(screen.queryByText('Settings')).toBeOnTheScreen(); | ||
}); | ||
``` | ||
|
||
</TabItem> | ||
</Tabs> | ||
|
||
test('shows profile screen when View Profile is pressed', () => { | ||
render(<Navigation />); | ||
`push` function test example: | ||
|
||
fireEvent.press(screen.getByText('View Profile')); | ||
<Tabs groupId="example" queryString="example"> | ||
<TabItem value="static" label="Static" default> | ||
|
||
expect(screen.getByText('My Profile')).toBeOnTheScreen(); | ||
```js | ||
|
||
``` | ||
|
||
</TabItem> | ||
<TabItem value="dynamic" label="Dynamic"> | ||
|
||
```js | ||
import { expect, test } from '@jest/globals'; | ||
import { fireEvent, render, screen } from '@testing-library/react-native'; | ||
import { | ||
createNavigationContainerRef, | ||
NavigationContainer, | ||
} from '@react-navigation/native'; | ||
import { RootNavigator } from './RootNavigator'; | ||
|
||
test('pushes settings screen twice', () => { | ||
const navigation = createNavigationContainerRef(); | ||
render( | ||
<NavigationContainer ref={navigation}> | ||
<RootNavigator /> | ||
</NavigationContainer> | ||
); | ||
|
||
const button = screen.getByText('Push Settings'); | ||
fireEvent.press(button); | ||
fireEvent.press(button); | ||
|
||
expect(navigation.getState().routes.map((route) => route.name)).toStrictEqual( | ||
['Profile', 'Settings', 'Settings'] | ||
); | ||
expect(screen.queryByText('Profile')).not.toBeOnTheScreen(); | ||
expect(screen.queryByText('Settings')).toBeOnTheScreen(); | ||
}); | ||
``` | ||
|
||
</TabItem> | ||
</Tabs> | ||
|
||
For writing tests that include times functions you will need to use [Fake Timers](https://jestjs.io/docs/timer-mocks). They will replace times function implementation to use time from the fake clock. | ||
|
||
Let's add another button to the Profile screen which uses `setTimeout`: | ||
|
||
<Tabs groupId="example" queryString="example"> | ||
<TabItem value="static" label="Static" default> | ||
|
||
```js | ||
|
||
``` | ||
|
||
</TabItem> | ||
<TabItem value="dynamic" label="Dynamic"> | ||
|
||
```js | ||
const Profile = ({ navigation }) => { | ||
return ( | ||
<View> | ||
<Text>Profile</Text> | ||
<Button | ||
onPress={() => navigation.navigate('Settings')} | ||
title="Navigate to Settings" | ||
/> | ||
<Button | ||
onPress={() => navigation.push('Settings')} | ||
title="Push Settings" | ||
/> | ||
// Added button | ||
<Button | ||
onPress={() => setTimeout(() => navigation.navigate('Settings'), 10000)} | ||
title="Navigate to Settings with 10000 ms delay" | ||
/> | ||
</View> | ||
); | ||
}; | ||
``` | ||
|
||
</TabItem> | ||
</Tabs> | ||
|
||
Fake timers test example: | ||
|
||
<Tabs groupId="example" queryString="example"> | ||
<TabItem value="static" label="Static" default> | ||
|
||
```js | ||
|
||
``` | ||
|
||
</TabItem> | ||
<TabItem value="dynamic" label="Dynamic"> | ||
|
||
```js name='Testing with jest' | ||
import * as React from 'react'; | ||
import { screen, render, fireEvent } from '@testing-library/react-native'; | ||
```js | ||
import { expect, jest, test } from '@jest/globals'; | ||
import { act, fireEvent, render, screen } from '@testing-library/react-native'; | ||
import { NavigationContainer } from '@react-navigation/native'; | ||
import { RootNavigator } from './RootNavigator'; | ||
|
||
test('shows profile screen when View Profile is pressed', () => { | ||
test('navigates to settings screen after 10000 ms delay', () => { | ||
// Enable fake timers | ||
jest.useFakeTimers(); | ||
|
||
render( | ||
<NavigationContainer> | ||
<RootNavigator /> | ||
</NavigationContainer> | ||
); | ||
|
||
fireEvent.press(screen.getByText('View Profile')); | ||
fireEvent.press(screen.getByText('Navigate to Settings with 10000 ms delay')); | ||
|
||
expect(screen.queryByText('Profile')).toBeOnTheScreen(); | ||
expect(screen.queryByText('Settings')).not.toBeOnTheScreen(); | ||
|
||
// jest.advanceTimersByTime causes React state updates | ||
// So it should be wrapped into act | ||
act(() => jest.advanceTimersByTime(10000)); | ||
|
||
expect(screen.getByText('My Profile')).toBeOnTheScreen(); | ||
expect(screen.queryByText('Profile')).not.toBeOnTheScreen(); | ||
expect(screen.queryByText('Settings')).toBeOnTheScreen(); | ||
}); | ||
``` | ||
|
||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems we're missing a return statement