Skip to content

Adding tests for React Navigation Drawer Navigator #624

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

Merged
merged 7 commits into from
Jan 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions examples/reactnavigation/jest-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'react-native-gesture-handler/jestSetup';

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;
});

// Silence the warning: Animated: `useNativeDriver` is not supported because the native animated module is missing
jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');
2 changes: 1 addition & 1 deletion examples/reactnavigation/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module.exports = {
preset: 'react-native',
setupFiles: ['./node_modules/react-native-gesture-handler/jestSetup.js'],
setupFiles: ['./jest-setup.js'],
transformIgnorePatterns: [
'node_modules/(?!(jest-)?react-native|@react-native-community|@react-navigation)',
],
Expand Down
1 change: 1 addition & 0 deletions examples/reactnavigation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
},
"dependencies": {
"@react-native-community/masked-view": "^0.1.9",
"@react-navigation/drawer": "^5.11.4",
"@react-navigation/native": "^5.1.6",
"@react-navigation/stack": "^5.2.13",
"prop-types": "^15.7.2",
Expand Down
2 changes: 1 addition & 1 deletion examples/reactnavigation/src/App.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'react-native-gesture-handler';
import React from 'react';
import { StatusBar, StyleSheet, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
Expand All @@ -9,7 +10,6 @@ export default function App() {
<NavigationContainer>
<View style={styles.container}>
<StatusBar barStyle="dark-content" />

<AppNavigator />
</View>
</NavigationContainer>
Expand Down
1 change: 0 additions & 1 deletion examples/reactnavigation/src/AppNavigator.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'react-native-gesture-handler';
import * as React from 'react';
import { createStackNavigator } from '@react-navigation/stack';

Expand Down
13 changes: 13 additions & 0 deletions examples/reactnavigation/src/DrawerApp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'react-native-gesture-handler';
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';

import DrawerAppNavigator from './DrawerAppNavigator';

export default function DrawerApp() {
return (
<NavigationContainer>
<DrawerAppNavigator />
</NavigationContainer>
);
}
35 changes: 35 additions & 0 deletions examples/reactnavigation/src/DrawerAppNavigator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import {createDrawerNavigator} from '@react-navigation/drawer';
import {View, Button, Text} from 'react-native';

const { Screen, Navigator } = createDrawerNavigator();

function HomeScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Welcome!</Text>
<Button
onPress={() => navigation.navigate('Notifications')}
title="Go to notifications"
/>
</View>
);
}

function NotificationsScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>This is the notifications screen</Text>
<Button onPress={() => navigation.goBack()} title="Go back home" />
</View>
);
}

export default function Navigation() {
return (
<Navigator>
<Screen name="Home" component={HomeScreen} />
<Screen name="Notifications" component={NotificationsScreen} />
</Navigator>
);
}
3 changes: 0 additions & 3 deletions examples/reactnavigation/src/__tests__/AppNavigator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import { render, fireEvent } from '@testing-library/react-native';

import AppNavigator from '../AppNavigator';

// Silence the warning https://github.com/facebook/react-native/issues/11094#issuecomment-263240420
jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');

describe('Testing react navigation', () => {
test('page contains the header and 10 items', async () => {
const component = (
Expand Down
39 changes: 39 additions & 0 deletions examples/reactnavigation/src/__tests__/DrawerAppNavigator.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { render, fireEvent } from '@testing-library/react-native';

import DrawerAppNavigator from '../DrawerAppNavigator';

describe('Testing react navigation', () => {
test('screen contains a button linking to the notifications page', async () => {
const component = (
<NavigationContainer>
<DrawerAppNavigator />
</NavigationContainer>
);

const { findByText, findAllByText } = render(component);
const button = await findByText('Go to notifications');

expect(button).toBeTruthy();
});

test('clicking on the button takes you to the notifications screen', async () => {
const component = (
<NavigationContainer>
<DrawerAppNavigator />
</NavigationContainer>
);

const { queryByText, findByText } = render(component);
const oldScreen = queryByText('Welcome!');
const button = await findByText('Go to notifications');

expect(oldScreen).toBeTruthy();

fireEvent(button, 'press');
const newScreen = await findByText('This is the notifications screen');

expect(newScreen).toBeTruthy();
});
});
155 changes: 153 additions & 2 deletions website/docs/ReactNavigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ title: React Navigation

This section deals with integrating `@testing-library/react-native` with `react-navigation`, using Jest.

## Setting up
## Stack Navigator

### Setting up

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.

Expand Down Expand Up @@ -122,7 +124,7 @@ const styles = StyleSheet.create({
});
```

## Setting up the test environment
### Setting up the test environment

Install required dev dependencies:

Expand All @@ -147,6 +149,8 @@ Notice the 2 entries that don't come with the default React Native project:
- `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
- `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).

### Example tests

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.

Let's add 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:
Expand Down Expand Up @@ -198,6 +202,153 @@ describe('Testing react navigation', () => {
});
```

## Drawer Navigator

Testing the Drawer Navigation requires an additional setup step for mocking the Reanimated library.

### Setting up

Install the packages required for React Navigation. For this example, we will use a [drawer navigator](https://reactnavigation.org/docs/drawer-navigator/) to transition between a home screen and an additional screen.

```
$ yarn add @react-native-community/masked-view @react-navigation/native @react-navigation/drawer react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-screens
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$ yarn add @react-native-community/masked-view @react-navigation/native @react-navigation/drawer react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-screens
$ yarn add @react-navigation/native @react-navigation/drawer react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-screens

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@satya164 You are suggesting the masked-view is not needed? It's listed in the install instructions on the React Navigation website: https://reactnavigation.org/docs/getting-started#installing-dependencies-into-a-bare-react-native-project

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's only necessary for stack navigator.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this PR would create one set of instructions for both the stack navigator and the drawer navigator - should I just leave the masked view here? Or add instructions for the stack navigator only?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me

```

Create a [`./DrawerAppNavigator.js`](https://github.com/callstack/react-native-testing-library/blob/master/examples/reactnavigation/src/DrawerAppNavigator.js) component which will list the navigation stack:

```jsx
import 'react-native-gesture-handler';
import React from 'react';
import {createDrawerNavigator} from '@react-navigation/drawer';

const { Screen, Navigator } = createDrawerNavigator();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to use the style followed in official docs without destructring

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


export default function Navigation() {
return (
<Navigator>
<Screen name="Home" component={HomeScreen} />
<Screen name="Notifications" component={NotificationsScreen} />
</Navigator>
);
}
```

Create your two screens which we will transition to and from:

```jsx
function HomeScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Welcome!</Text>
<Button
onPress={() => navigation.navigate('Notifications')}
title="Go to notifications"
/>
</View>
);
}

function NotificationsScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>This is the notifications screen</Text>
<Button onPress={() => navigation.goBack()} title="Go back home" />
</View>
);
}
```

### Setting up the test environment

Install required dev dependencies:

```
$ yarn add -D jest @testing-library/react-native
```

Create a [`mock file`](https://github.com/callstack/react-native-testing-library/blob/master/examples/reactnavigation/jest-mocks.js) necessary for your tests:

```jsx
import 'react-native-gesture-handler/jestSetup';

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;
});

// Silence the warning: Animated: `useNativeDriver` is not supported because the native animated module is missing
jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');
```

Create your `jest.config.js` file (or place the following properties in your `package.json` as a "jest" property)

```js
module.exports = {
preset: 'react-native',
setupFiles: ['./jest-mocks.js'],
transformIgnorePatterns: [
'node_modules/(?!(jest-)?react-native|@react-native-community|@react-navigation)',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'node_modules/(?!(jest-)?react-native|@react-native-community|@react-navigation)',
'node_modules/(?!(jest-)?react-native|@react-native-community)',

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the @react-navigation here makes the tests fail:

Zrzut ekranu 2020-12-10 o 17 22 07

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. I assumed Jest already handled asset files with react-native preset cc @thymikee

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jest doesn't transpile node_modules by default. RN preset adjusts the configuration so that only node_modules other than react-native* and @react-native-community* are not transpiled. @react-navigation is not there, so we need to add it manually.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thymikee I'm talking about assets, React Navigation's code doesn't need to be compiled to run in Node.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

png is passed to a custom transformer: https://github.com/facebook/react-native/blob/master/jest-preset.js#L19 and transformers obey "transformIgnorePatterns", so here's why it fails.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be fixed by using moduleNameMapper instead of transformer: https://jestjs.io/docs/en/webpack#handling-static-assets

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thymikee do you know why it doesn't use moduleNameMapper? probably so people wouldn't have to specify it again when overriding?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why, would have to dig through git blame. Jest handles merging moduleNameMapper gracefully, same as transforms

],
};
```

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.

This setup is copied from the [React Navigation documentation](https://reactnavigation.org/docs/testing/).

### Example tests

For this example, we are going to test out two things. The first thing is that the screen is loaded correctly. The second, and most important, is that the page will transition to the notifications screen when the button is tapped on.

Let's add a [`DrawerAppNavigator.test.js`](https://github.com/callstack/react-native-testing-library/blob/master/examples/reactnavigation/src/__tests__/DrawerAppNavigator.js) file in `src/__tests__` directory:

```jsx
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { render, fireEvent } from '@testing-library/react-native';

import DrawerAppNavigator from '../DrawerAppNavigator';

describe('Testing react navigation', () => {
test('screen contains a button linking to the notifications page', async () => {
const component = (
<NavigationContainer>
<DrawerAppNavigator />
</NavigationContainer>
);

const { findByText, findAllByText } = render(component);
const button = await findByText('Go to notifications');

expect(button).toBeTruthy();
});

test('clicking on the button takes you to the notifications screen', async () => {
const component = (
<NavigationContainer>
<DrawerAppNavigator />
</NavigationContainer>
);

const { queryByText, findByText } = render(component);
const oldScreen = queryByText('Welcome!');
const button = await findByText('Go to notifications');

expect(oldScreen).toBeTruthy();

fireEvent(button, 'press');
const newScreen = await findByText('This is the notifications screen');

expect(newScreen).toBeTruthy();
});
});
```

## Running tests

To run the tests, place a test script inside your `package.json`
Expand Down