Skip to content

Commit ad66a54

Browse files
aleclarsonthymikeemdjastrzebski
committed
feat: call "cleanup" automatically (#238)
* feat: add "cleanup" function * docs: add "cleanup" section * feat: automatically call "cleanup" after each test ...if an "afterEach" global function is defined, and process.env.RTL_SKIP_AUTO_CLEANUP is falsy. Taken from: https://github.com/testing-library/react-testing-library/blob/14670debd45236d2c5d0a61a83dadc72af1bef7c/src/index.js * docs: mention automatic cleanup * add within * fix lint * Updated tests, added auto-cleanup test * Added ways to prevent auto cleanup * Small fix * Code review changes * Update website/docs/API.md Co-authored-by: Michał Pierzchała <[email protected]> * More changes * Removed afterEach(cleanup) calls in examples * Code cleanup Co-authored-by: Michał Pierzchała <[email protected]> Co-authored-by: Maciej Jastrzebski <[email protected]>
1 parent 40ed268 commit ad66a54

15 files changed

+151
-31
lines changed

dont-cleanup-after-each.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
process.env.RNTL_SKIP_AUTO_CLEANUP = true;

examples/reactnavigation/src/__tests__/AppNavigator.test.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import React from 'react';
22
import { NavigationContainer } from '@react-navigation/native';
3-
import { render, fireEvent, cleanup } from 'react-native-testing-library';
3+
import { render, fireEvent } from 'react-native-testing-library';
44

55
import AppNavigator from '../AppNavigator';
66

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

1010
describe('Testing react navigation', () => {
11-
afterEach(cleanup);
12-
1311
test('page contains the header and 10 items', () => {
1412
const component = (
1513
<NavigationContainer>

examples/redux/components/AddTodo.test.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import React from 'react';
22
import { Provider } from 'react-redux';
3-
import { cleanup, fireEvent, render } from 'react-native-testing-library';
3+
import { fireEvent, render } from 'react-native-testing-library';
44
import configureStore from '../store';
55
import AddTodo from './AddTodo';
66

77
describe('Application test', () => {
8-
afterEach(cleanup);
9-
108
test('adds a new test when entry has been included', () => {
119
const store = configureStore();
1210

examples/redux/components/TodoList.test.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import React from 'react';
22
import { Provider } from 'react-redux';
3-
import { cleanup, fireEvent, render } from 'react-native-testing-library';
3+
import { fireEvent, render } from 'react-native-testing-library';
44
import configureStore from '../store';
55
import TodoList from './TodoList';
66

77
describe('Application test', () => {
8-
afterEach(cleanup);
9-
108
test('it should execute with a store with 4 elements', () => {
119
const initialState = {
1210
todos: [

examples/redux/reducers/todoReducer.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export default function todoReducer(state = [], action) {
66
return state.concat(action.payload);
77

88
case actions.REMOVE:
9-
console.log(action);
109
return state.filter((todo) => todo.id !== action.payload.id);
1110

1211
case actions.MODIFY:

pure.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// makes it so people can import from 'react-native-testing-library/pure'
2+
module.exports = require('./build/pure');

src/__tests__/auto-cleanup-skip.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React from 'react';
2+
import { View } from 'react-native';
3+
4+
let render;
5+
beforeAll(() => {
6+
process.env.RNTL_SKIP_AUTO_CLEANUP = 'true';
7+
const rtl = require('../');
8+
render = rtl.render;
9+
});
10+
11+
let isMounted = false;
12+
13+
class Test extends React.Component<*> {
14+
componentDidMount() {
15+
isMounted = true;
16+
}
17+
18+
componentWillUnmount() {
19+
isMounted = false;
20+
if (this.props.onUnmount) {
21+
this.props.onUnmount();
22+
}
23+
}
24+
render() {
25+
return <View />;
26+
}
27+
}
28+
29+
// This just verifies that by importing RNTL in pure mode in an environment which supports
30+
// afterEach (like jest) we won't get automatic cleanup between tests.
31+
test('component is mounted, but not umounted before test ends', () => {
32+
const fn = jest.fn();
33+
render(<Test onUnmount={fn} />);
34+
expect(fn).not.toHaveBeenCalled();
35+
});
36+
37+
test('component is NOT automatically umounted after first test ends', () => {
38+
expect(isMounted).toEqual(true);
39+
});

src/__tests__/auto-cleanup.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from 'react';
2+
import { View } from 'react-native';
3+
import { render } from '..';
4+
5+
let isMounted = false;
6+
7+
class Test extends React.Component<*> {
8+
componentDidMount() {
9+
isMounted = true;
10+
}
11+
12+
componentWillUnmount() {
13+
isMounted = false;
14+
if (this.props.onUnmount) {
15+
this.props.onUnmount();
16+
}
17+
}
18+
render() {
19+
return <View />;
20+
}
21+
}
22+
23+
// This just verifies that by importing RNTL in an environment which supports afterEach (like jest)
24+
// we'll get automatic cleanup between tests.
25+
test('component is mounted, but not umounted before test ends', () => {
26+
const fn = jest.fn();
27+
render(<Test onUnmount={fn} />);
28+
expect(fn).not.toHaveBeenCalled();
29+
});
30+
31+
test('component is automatically umounted after first test ends', () => {
32+
expect(isMounted).toEqual(false);
33+
});

src/__tests__/cleanup.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/* eslint-disable react/no-multi-comp */
33
import React from 'react';
44
import { View } from 'react-native';
5-
import { cleanup, render } from '..';
5+
import { cleanup, render } from '../pure';
66

77
class Test extends React.Component<*> {
88
componentWillUnmount() {

src/__tests__/waitFor.test.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ test('waits for element until timeout is met', async () => {
5555
await expect(
5656
waitFor(() => getByText('Fresh'), { timeout: 100 })
5757
).rejects.toThrow();
58+
59+
// Async action ends after 300ms and we only waited 100ms, so we need to wait
60+
// for the remaining async actions to finish
61+
await waitFor(() => getByText('Fresh'));
5862
});
5963

6064
test('waits for element with custom interval', async () => {

src/index.js

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
// @flow
2-
import act from './act';
3-
import cleanup from './cleanup';
4-
import fireEvent from './fireEvent';
52
import flushMicrotasksQueue from './flushMicrotasksQueue';
6-
import render from './render';
7-
import shallow from './shallow';
8-
import waitFor, { waitForElement } from './waitFor';
9-
import within from './within';
3+
import { cleanup } from './pure';
104

11-
export { act };
12-
export { cleanup };
13-
export { fireEvent };
14-
export { flushMicrotasksQueue };
15-
export { render };
16-
export { shallow };
17-
export { waitFor, waitForElement };
18-
export { within };
5+
// If we're running in a test runner that supports afterEach
6+
// then we'll automatically run cleanup afterEach test
7+
// this ensures that tests run in isolation from each other
8+
// if you don't like this then either import the `pure` module
9+
// or set the RNTL_SKIP_AUTO_CLEANUP env variable to 'true'.
10+
if (typeof afterEach === 'function' && !process.env.RNTL_SKIP_AUTO_CLEANUP) {
11+
// eslint-disable-next-line no-undef
12+
afterEach(async () => {
13+
await flushMicrotasksQueue();
14+
cleanup();
15+
});
16+
}
17+
18+
export * from './pure';

src/pure.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// @flow
2+
import act from './act';
3+
import cleanup from './cleanup';
4+
import fireEvent from './fireEvent';
5+
import flushMicrotasksQueue from './flushMicrotasksQueue';
6+
import render from './render';
7+
import shallow from './shallow';
8+
import waitFor, { waitForElement } from './waitFor';
9+
import within from './within';
10+
11+
export { act };
12+
export { cleanup };
13+
export { fireEvent };
14+
export { flushMicrotasksQueue };
15+
export { render };
16+
export { shallow };
17+
export { waitFor, waitForElement };
18+
export { within };

website/docs/API.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@ Re-render the in-memory tree with a new root element. This simulates a React upd
7474
unmount(): void
7575
```
7676

77-
Unmount the in-memory tree, triggering the appropriate lifecycle events
77+
Unmount the in-memory tree, triggering the appropriate lifecycle events.
78+
79+
:::note
80+
Usually you should not need to call `unmount` as it is done automatically if your test runner supports `afterEach` hook (like Jest, mocha, Jasmine).
81+
:::
7882

7983
### `debug`
8084

@@ -123,10 +127,14 @@ const cleanup: () => void;
123127

124128
Unmounts React trees that were mounted with `render`.
125129

130+
:::info
131+
Please note that this is done automatically if the testing framework you're using supports the `afterEach` global (like mocha, Jest, and Jasmine). If not, you will need to do manual cleanups after each test.
132+
:::
133+
126134
For example, if you're using the `jest` testing framework, then you would need to use the `afterEach` hook like so:
127135

128136
```jsx
129-
import { cleanup, render } from 'react-native-testing-library';
137+
import { cleanup, render } from 'react-native-testing-library/pure';
130138
import { View } from 'react-native';
131139

132140
afterEach(cleanup);

website/docs/Migration20.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,30 @@ This guides describes major steps involved in migrating your testing code from u
99

1010
Node 8 reached its EOL more than 5 months ago, so it's about time to target the library to Node 10. If you used lower version, you'll have to upgrade to v10, but we suggest using the latest LTS version.
1111

12+
## Auto Cleanup
13+
14+
`cleanup()` function is now called automatically after every test, if your testing framework supports `afterEach` hook (like Jest, mocha, and Jasmine).
15+
16+
You should be able to safely remove all `afterEach(cleanup)` calls in your code.
17+
18+
This change might break your code, if you tests are not isolated, i.e. you call `render` outside `test` block. Generally, you should [keep your tests isolated](https://kentcdodds.com/blog/test-isolation-with-react), but if you can't or don't want to do this right away you can prevent this behavior using any of the foloowing ways:
19+
20+
1. by importing `'react-native-testing-library/pure'` instead of `'react-native-testing-library'`
21+
22+
2. by importing `'react-native-testing-library/dont-cleanup-after-each'` before importing `'react-native-testing-library'`. You can do it in a global way by using Jest's `setupFiles` like this:
23+
24+
```js
25+
{
26+
setupFiles: ['react-native-testing-library/dont-cleanup-after-each'];
27+
}
28+
```
29+
30+
3. by setting `RTNL_SKIP_AUTO_CLEANUP` env variable to `true`. You can do this with `cross-evn` like this:
31+
32+
```sh
33+
cross-env RNTL_SKIP_AUTO_CLEANUP=true jest
34+
```
35+
1236
## WaitFor API changes
1337

1438
`waitForElement` function has been renamed to `waitFor` for consistency with React Testing Library. Additionally the signature has slightly changed from:

website/docs/ReactNavigation.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export default function HomeScreen({ navigation }) {
5454
new Array(20).fill(null).map((_, idx) => idx + 1)
5555
);
5656

57-
const onOpacityPress = item => navigation.navigate('Details', item);
57+
const onOpacityPress = (item) => navigation.navigate('Details', item);
5858

5959
return (
6060
<View>
@@ -162,8 +162,6 @@ import AppNavigator from '../AppNavigator';
162162
jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');
163163

164164
describe('Testing react navigation', () => {
165-
afterEach(cleanup);
166-
167165
test('page contains the header and 10 items', () => {
168166
const component = (
169167
<NavigationContainer>

0 commit comments

Comments
 (0)