Skip to content

Commit 95b4889

Browse files
cross19xxthymikee
andauthored
docs: add example of testing React Navigation (#277)
* Proof of concept - CodeSandbox embed * Prettier fix * Moved examples below API Reference * Created the examples directory Includes react-navigation example * Article on React Navigation * Specify rootDir for testing Prevents CI to run example tests * remove jest-transform-stub and identity-obj-proxy, add transformIgnorePatterns * update docs * rearrange * remove non-essential files * rename Navigation -> AppNavigator * reword comment * add links and explanations * remove index file, because it's not an app anyway Co-authored-by: Michał Pierzchała <[email protected]>
1 parent 1efb814 commit 95b4889

File tree

15 files changed

+438
-4
lines changed

15 files changed

+438
-4
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ node_modules
44
build
55
.idea
66
.DS_Store
7+
8+
# Ignore lock files in examples for now
9+
examples/**/yarn.lock

docs/ReactNavigation.md

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
---
2+
id: react-navigation
3+
title: React Navigation
4+
---
5+
6+
This section deals with integrating `react-native-testing-library` with `react-navigation`, using Jest.
7+
8+
## Setting up
9+
10+
Install the packages required for React Navigation. For this example, we will use a [stack navigator](https://reactnavigation.org/docs/stack-navigator/) to transition to the second page when any of the items are clicked on.
11+
12+
```
13+
$ yarn add @react-native-community/masked-view @react-navigation/native @react-navigation/stack react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-screens
14+
```
15+
16+
Create an [`./AppNavigator.js`](https://github.com/callstack/react-native-testing-library/blob/master/examples/reactnavigation/src/AppNavigator.js) component which will list the navigation stack:
17+
18+
```jsx
19+
import 'react-native-gesture-handler';
20+
import React from 'react';
21+
import { createStackNavigator } from '@react-navigation/stack';
22+
23+
import HomeScreen from './screens/HomeScreen';
24+
import DetailsScreen from './screens/DetailsScreen';
25+
26+
const { Screen, Navigator } = createStackNavigator();
27+
28+
export default function Navigation() {
29+
const options = {};
30+
31+
return (
32+
<Navigator>
33+
<Screen name="Home" component={HomeScreen} />
34+
<Screen options={options} name="Details" component={DetailsScreen} />
35+
</Navigator>
36+
);
37+
}
38+
```
39+
40+
Create your two screens which we will transition to and from them. The homescreen, found in [`./screens/HomeScreen.js`](https://github.com/callstack/react-native-testing-library/blob/master/examples/reactnavigation/src/screens/HomeScreen.js), contains a list of elements presented in a list view. On tap of any of these items will move to the details screen with the item number:
41+
42+
```jsx
43+
import React from 'react';
44+
import {
45+
Text,
46+
View,
47+
FlatList,
48+
TouchableOpacity,
49+
StyleSheet,
50+
} from 'react-native';
51+
52+
export default function HomeScreen({ navigation }) {
53+
const [items] = React.useState(
54+
new Array(20).fill(null).map((_, idx) => idx + 1)
55+
);
56+
57+
const onOpacityPress = item => navigation.navigate('Details', item);
58+
59+
return (
60+
<View>
61+
<Text style={styles.header}>List of numbers from 1 to 20</Text>
62+
<FlatList
63+
keyExtractor={(_, idx) => `${idx}`}
64+
data={items}
65+
renderItem={({ item }) => (
66+
<TouchableOpacity
67+
onPress={() => onOpacityPress(item)}
68+
style={styles.row}
69+
>
70+
<Text>Item number {item}</Text>
71+
</TouchableOpacity>
72+
)}
73+
/>
74+
</View>
75+
);
76+
}
77+
78+
const divider = '#DDDDDD';
79+
80+
const styles = StyleSheet.create({
81+
header: {
82+
fontSize: 20,
83+
textAlign: 'center',
84+
marginVertical: 16,
85+
},
86+
row: {
87+
paddingVertical: 16,
88+
paddingHorizontal: 24,
89+
borderBottomColor: divider,
90+
borderBottomWidth: 1,
91+
},
92+
});
93+
```
94+
95+
The details screen, found in [`./screens/DetailsScreen.js`](https://github.com/callstack/react-native-testing-library/blob/master/examples/reactnavigation/src/screens/DetailsScreen.js), contains a header with the item number passed from the home screen:
96+
97+
```jsx
98+
// ./screens/DetailsScreen.js
99+
import React from 'react';
100+
import { Text, StyleSheet, View } from 'react-native';
101+
102+
export default function DetailsScreen(props) {
103+
const item = Number.parseInt(props.route.params, 10);
104+
105+
return (
106+
<View>
107+
<Text style={styles.header}>Showing details for {item}</Text>
108+
<Text style={styles.body}>the number you have chosen is {item}</Text>
109+
</View>
110+
);
111+
}
112+
113+
const styles = StyleSheet.create({
114+
header: {
115+
fontSize: 20,
116+
textAlign: 'center',
117+
marginVertical: 16,
118+
},
119+
body: {
120+
textAlign: 'center',
121+
},
122+
});
123+
```
124+
125+
## Setting up the test environment
126+
127+
Install required dev dependencies:
128+
129+
```
130+
$ yarn add -D jest react-native-testing-library
131+
```
132+
133+
Create your `jest.config.js` file (or place the following properties in your `package.json` as a "jest" property)
134+
135+
```js
136+
module.exports = {
137+
preset: 'react-native',
138+
setupFiles: ['./node_modules/react-native-gesture-handler/jestSetup.js'],
139+
transformIgnorePatterns: [
140+
'node_modules/(?!(jest-)?react-native|@react-native-community|@react-navigation)',
141+
],
142+
};
143+
```
144+
145+
Notice the 2 entries that don't come with the default React Native project:
146+
147+
- `setupFiles` – an array of files that Jest is going to execute before running your tests. In this case, we run `react-native-gesture-handler/jestSetup.js` which sets up necessary mocks for `react-native-gesture-handler` native module
148+
- `transformIgnorePatterns` – an array of paths that Jest ignores when transforming code. In this case, the negative lookahead regular expression is used, to tell Jest to transform (with Babel) every package inside `node_modules/` that starts with `react-native`, `@react-native-community` or `@react-navigation` (added by us, the rest is in `react-native` preset by default, so you don't have to worry about it).
149+
150+
For this example, we are going to test out two things. The first thing is that the page is laid out as expected. The second, and most important, is that the page will transition to the detail screen when any item is tapped on.
151+
152+
Let's a [`AppNavigator.test.js`](https://github.com/callstack/react-native-testing-library/blob/master/examples/reactnavigation/src/__tests__/AppNavigator.js) file in `src/__tests__` directory:
153+
154+
```jsx
155+
import React from 'react';
156+
import { NavigationContainer } from '@react-navigation/native';
157+
import { render, fireEvent, cleanup } from 'react-native-testing-library';
158+
159+
import AppNavigator from '../AppNavigator';
160+
161+
// Silence the warning https://github.com/facebook/react-native/issues/11094#issuecomment-263240420
162+
jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');
163+
164+
describe('Testing react navigation', () => {
165+
afterEach(cleanup);
166+
167+
test('page contains the header and 10 items', () => {
168+
const component = (
169+
<NavigationContainer>
170+
<AppNavigator />
171+
</NavigationContainer>
172+
);
173+
174+
const { getByText, getAllByText } = render(component);
175+
176+
const header = getByText('List of numbers from 1 to 20');
177+
const items = getAllByText(/Item number/);
178+
179+
expect(header).toBeTruthy();
180+
expect(items.length).toBe(10);
181+
});
182+
183+
test('clicking on one item takes you to the details screen', async () => {
184+
const component = (
185+
<NavigationContainer>
186+
<AppNavigator />
187+
</NavigationContainer>
188+
);
189+
190+
const { getByText } = render(component);
191+
const toClick = getByText('Item number 5');
192+
193+
fireEvent(toClick, 'press');
194+
const newHeader = getByText('Showing details for 5');
195+
const newBody = getByText('the number you have chosen is 5');
196+
197+
expect(newHeader).toBeTruthy();
198+
expect(newBody).toBeTruthy();
199+
});
200+
});
201+
```
202+
203+
## Running tests
204+
205+
To run the tests, place a test script inside your `package.json`
206+
207+
```json
208+
{
209+
"scripts": {
210+
"test": "jest"
211+
}
212+
}
213+
```
214+
215+
And run the `test` script with `npm test` or `yarn test`.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
presets: ['module:metro-react-native-babel-preset'],
3+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
preset: 'react-native',
3+
setupFiles: ['./node_modules/react-native-gesture-handler/jestSetup.js'],
4+
transformIgnorePatterns: [
5+
'node_modules/(?!(jest-)?react-native|@react-native-community|@react-navigation)',
6+
],
7+
};

examples/reactnavigation/package.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "react-navigation-example",
3+
"description": "Testing react-navigation with react-native-testing-library",
4+
"version": "0.0.1",
5+
"private": true,
6+
"scripts": {
7+
"test": "jest"
8+
},
9+
"dependencies": {
10+
"@react-native-community/masked-view": "^0.1.9",
11+
"@react-navigation/native": "^5.1.6",
12+
"@react-navigation/stack": "^5.2.13",
13+
"prop-types": "^15.7.2",
14+
"react": "^16.13.1",
15+
"react-native": "^0.62.2",
16+
"react-native-gesture-handler": "^1.6.1",
17+
"react-native-reanimated": "^1.8.0",
18+
"react-native-safe-area-context": "^0.7.3",
19+
"react-native-screens": "^2.5.0"
20+
},
21+
"devDependencies": {
22+
"@babel/core": "^7.9.0",
23+
"@babel/runtime": "^7.9.2",
24+
"babel-jest": "^25.4.0",
25+
"jest": "^25.4.0",
26+
"metro-react-native-babel-preset": "^0.59.0",
27+
"react-native-testing-library": "^1.13.0",
28+
"react-test-renderer": "^16.13.1"
29+
}
30+
}

examples/reactnavigation/src/App.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React from 'react';
2+
import { StatusBar, StyleSheet, View } from 'react-native';
3+
import { NavigationContainer } from '@react-navigation/native';
4+
5+
import AppNavigator from './AppNavigator';
6+
7+
export default function App() {
8+
return (
9+
<NavigationContainer>
10+
<View style={styles.container}>
11+
<StatusBar barStyle="dark-content" />
12+
13+
<AppNavigator />
14+
</View>
15+
</NavigationContainer>
16+
);
17+
}
18+
19+
const styles = StyleSheet.create({
20+
container: {
21+
flex: 1,
22+
},
23+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import 'react-native-gesture-handler';
2+
import React from 'react';
3+
import { createStackNavigator } from '@react-navigation/stack';
4+
5+
import HomeScreen from './screens/HomeScreen';
6+
import DetailsScreen from './screens/DetailsScreen';
7+
8+
const { Screen, Navigator } = createStackNavigator();
9+
10+
export default function Navigation() {
11+
const options = {};
12+
13+
return (
14+
<Navigator>
15+
<Screen name="Home" component={HomeScreen} />
16+
<Screen options={options} name="Details" component={DetailsScreen} />
17+
</Navigator>
18+
);
19+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React from 'react';
2+
import { NavigationContainer } from '@react-navigation/native';
3+
import { render, fireEvent, cleanup } from 'react-native-testing-library';
4+
5+
import AppNavigator from '../AppNavigator';
6+
7+
// Silence the warning https://github.com/facebook/react-native/issues/11094#issuecomment-263240420
8+
jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');
9+
10+
describe('Testing react navigation', () => {
11+
afterEach(cleanup);
12+
13+
test('page contains the header and 10 items', () => {
14+
const component = (
15+
<NavigationContainer>
16+
<AppNavigator />
17+
</NavigationContainer>
18+
);
19+
20+
const { getByText, getAllByText } = render(component);
21+
22+
const header = getByText('List of numbers from 1 to 20');
23+
const items = getAllByText(/Item number/);
24+
25+
expect(header).toBeTruthy();
26+
expect(items.length).toBe(10);
27+
});
28+
29+
test('clicking on one item takes you to the details screen', async () => {
30+
const component = (
31+
<NavigationContainer>
32+
<AppNavigator />
33+
</NavigationContainer>
34+
);
35+
36+
const { getByText } = render(component);
37+
const toClick = getByText('Item number 5');
38+
39+
fireEvent(toClick, 'press');
40+
const newHeader = getByText('Showing details for 5');
41+
const newBody = getByText('the number you have chosen is 5');
42+
43+
expect(newHeader).toBeTruthy();
44+
expect(newBody).toBeTruthy();
45+
});
46+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from 'react';
2+
import { Text, StyleSheet, View } from 'react-native';
3+
4+
export default function DetailsScreen(props) {
5+
const item = Number.parseInt(props.route.params, 10);
6+
7+
return (
8+
<View>
9+
<Text style={styles.header}>Showing details for {item}</Text>
10+
<Text style={styles.body}>the number you have chosen is {item}</Text>
11+
</View>
12+
);
13+
}
14+
15+
const styles = StyleSheet.create({
16+
header: {
17+
fontSize: 20,
18+
textAlign: 'center',
19+
marginVertical: 16,
20+
},
21+
body: {
22+
textAlign: 'center',
23+
},
24+
});

0 commit comments

Comments
 (0)