Skip to content
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

feat: call "cleanup" automatically #238

Merged
merged 15 commits into from May 20, 2020
1 change: 1 addition & 0 deletions dont-cleanup-after-each.js
@@ -0,0 +1 @@
process.env.RNTL_SKIP_AUTO_CLEANUP = true;
4 changes: 1 addition & 3 deletions examples/reactnavigation/src/__tests__/AppNavigator.test.js
@@ -1,15 +1,13 @@
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { render, fireEvent, cleanup } from 'react-native-testing-library';
import { render, fireEvent } from 'react-native-testing-library';

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', () => {
afterEach(cleanup);

test('page contains the header and 10 items', () => {
const component = (
<NavigationContainer>
Expand Down
4 changes: 1 addition & 3 deletions examples/redux/components/AddTodo.test.js
@@ -1,12 +1,10 @@
import React from 'react';
import { Provider } from 'react-redux';
import { cleanup, fireEvent, render } from 'react-native-testing-library';
import { fireEvent, render } from 'react-native-testing-library';
import configureStore from '../store';
import AddTodo from './AddTodo';

describe('Application test', () => {
afterEach(cleanup);

test('adds a new test when entry has been included', () => {
const store = configureStore();

Expand Down
4 changes: 1 addition & 3 deletions examples/redux/components/TodoList.test.js
@@ -1,12 +1,10 @@
import React from 'react';
import { Provider } from 'react-redux';
import { cleanup, fireEvent, render } from 'react-native-testing-library';
import { fireEvent, render } from 'react-native-testing-library';
import configureStore from '../store';
import TodoList from './TodoList';

describe('Application test', () => {
afterEach(cleanup);

test('it should execute with a store with 4 elements', () => {
const initialState = {
todos: [
Expand Down
1 change: 0 additions & 1 deletion examples/redux/reducers/todoReducer.js
Expand Up @@ -6,7 +6,6 @@ export default function todoReducer(state = [], action) {
return state.concat(action.payload);

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

case actions.MODIFY:
Expand Down
2 changes: 2 additions & 0 deletions pure.js
@@ -0,0 +1,2 @@
// makes it so people can import from 'react-native-testing-library/pure'
module.exports = require('./build/pure');
39 changes: 39 additions & 0 deletions src/__tests__/auto-cleanup-skip.js
@@ -0,0 +1,39 @@
import React from 'react';
import { View } from 'react-native';

let render;
beforeAll(() => {
process.env.RNTL_SKIP_AUTO_CLEANUP = 'true';
const rtl = require('../');
render = rtl.render;
});

let isMounted = false;

class Test extends React.Component<*> {
componentDidMount() {
isMounted = true;
}

componentWillUnmount() {
isMounted = false;
if (this.props.onUnmount) {
this.props.onUnmount();
}
}
render() {
return <View />;
}
}

// This just verifies that by importing RNTL in pure mode in an environment which supports
// afterEach (like jest) we won't get automatic cleanup between tests.
test('component is mounted, but not umounted before test ends', () => {
const fn = jest.fn();
render(<Test onUnmount={fn} />);
expect(fn).not.toHaveBeenCalled();
});

test('component is NOT automatically umounted after first test ends', () => {
expect(isMounted).toEqual(true);
});
33 changes: 33 additions & 0 deletions src/__tests__/auto-cleanup.js
@@ -0,0 +1,33 @@
import React from 'react';
import { View } from 'react-native';
import { render } from '..';

let isMounted = false;

class Test extends React.Component<*> {
componentDidMount() {
isMounted = true;
}

componentWillUnmount() {
isMounted = false;
if (this.props.onUnmount) {
this.props.onUnmount();
}
}
render() {
return <View />;
}
}

// This just verifies that by importing RNTL in an environment which supports afterEach (like jest)
// we'll get automatic cleanup between tests.
test('component is mounted, but not umounted before test ends', () => {
const fn = jest.fn();
render(<Test onUnmount={fn} />);
expect(fn).not.toHaveBeenCalled();
});

test('component is automatically umounted after first test ends', () => {
expect(isMounted).toEqual(false);
});
2 changes: 1 addition & 1 deletion src/__tests__/cleanup.test.js
Expand Up @@ -2,7 +2,7 @@
/* eslint-disable react/no-multi-comp */
import React from 'react';
import { View } from 'react-native';
import { cleanup, render } from '..';
import { cleanup, render } from '../pure';

class Test extends React.Component<*> {
componentWillUnmount() {
Expand Down
4 changes: 4 additions & 0 deletions src/__tests__/waitFor.test.js
Expand Up @@ -55,6 +55,10 @@ test('waits for element until timeout is met', async () => {
await expect(
waitFor(() => getByText('Fresh'), { timeout: 100 })
).rejects.toThrow();

// Async action ends after 300ms and we only waited 100ms, so we need to wait
// for the remaining async actions to finish
await waitFor(() => getByText('Fresh'));
thymikee marked this conversation as resolved.
Show resolved Hide resolved
});

test('waits for element with custom interval', async () => {
Expand Down
30 changes: 15 additions & 15 deletions src/index.js
@@ -1,18 +1,18 @@
// @flow
import act from './act';
import cleanup from './cleanup';
import fireEvent from './fireEvent';
import flushMicrotasksQueue from './flushMicrotasksQueue';
thymikee marked this conversation as resolved.
Show resolved Hide resolved
import render from './render';
import shallow from './shallow';
import waitFor, { waitForElement } from './waitFor';
import within from './within';
import { cleanup } from './pure';

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

export * from './pure';
18 changes: 18 additions & 0 deletions src/pure.js
@@ -0,0 +1,18 @@
// @flow
import act from './act';
import cleanup from './cleanup';
import fireEvent from './fireEvent';
import flushMicrotasksQueue from './flushMicrotasksQueue';
import render from './render';
import shallow from './shallow';
import waitFor, { waitForElement } from './waitFor';
import within from './within';

export { act };
export { cleanup };
export { fireEvent };
export { flushMicrotasksQueue };
export { render };
export { shallow };
export { waitFor, waitForElement };
export { within };
12 changes: 10 additions & 2 deletions website/docs/API.md
Expand Up @@ -74,7 +74,11 @@ Re-render the in-memory tree with a new root element. This simulates a React upd
unmount(): void
```

Unmount the in-memory tree, triggering the appropriate lifecycle events
Unmount the in-memory tree, triggering the appropriate lifecycle events.

:::note
Usually you should not need to call `unmount` as it is done automatically if your test runner supports `afterEach` hook (like Jest, mocha, Jasmine).
:::

### `debug`

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

Unmounts React trees that were mounted with `render`.

:::info
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.
:::

For example, if you're using the `jest` testing framework, then you would need to use the `afterEach` hook like so:

```jsx
thymikee marked this conversation as resolved.
Show resolved Hide resolved
import { cleanup, render } from 'react-native-testing-library';
import { cleanup, render } from 'react-native-testing-library/pure';
import { View } from 'react-native';

afterEach(cleanup);
Expand Down
24 changes: 24 additions & 0 deletions website/docs/Migration20.md
Expand Up @@ -9,6 +9,30 @@ This guides describes major steps involved in migrating your testing code from u

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.

## Auto Cleanup

`cleanup()` function is now called automatically after every test, if your testing framework supports `afterEach` hook (like Jest, mocha, and Jasmine).

You should be able to safely remove all `afterEach(cleanup)` calls in your code.

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:

1. by importing `'react-native-testing-library/pure'` instead of `'react-native-testing-library'`

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:

```js
{
setupFiles: ['react-native-testing-library/dont-cleanup-after-each'];
}
```

3. by setting `RTNL_SKIP_AUTO_CLEANUP` env variable to `true`. You can do this with `cross-evn` like this:

```sh
cross-env RNTL_SKIP_AUTO_CLEANUP=true jest
```

## WaitFor API changes

`waitForElement` function has been renamed to `waitFor` for consistency with React Testing Library. Additionally the signature has slightly changed from:
Expand Down
4 changes: 1 addition & 3 deletions website/docs/ReactNavigation.md
Expand Up @@ -54,7 +54,7 @@ export default function HomeScreen({ navigation }) {
new Array(20).fill(null).map((_, idx) => idx + 1)
);

const onOpacityPress = item => navigation.navigate('Details', item);
const onOpacityPress = (item) => navigation.navigate('Details', item);

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

describe('Testing react navigation', () => {
afterEach(cleanup);

test('page contains the header and 10 items', () => {
const component = (
<NavigationContainer>
Expand Down