diff --git a/docs/API.md b/docs/API.md index b758e315a..550d4a94e 100644 --- a/docs/API.md +++ b/docs/API.md @@ -115,6 +115,45 @@ toJSON(): ReactTestRendererJSON | null Get the rendered component JSON representation, e.g. for snapshot testing. +## `cleanup` + +```ts +const cleanup: () => void +``` + +Unmounts React trees that were mounted with `render`. + +For example, if you're using the `jest` testing framework, then you would need to use the `afterEach` hook like so: + +```jsx +import { cleanup, render } from 'react-native-testing-library' +import { View } from 'react-native' + +afterEach(cleanup) + +it('renders a view', () => { + render() + // ... +}) +``` + +The `afterEach(cleanup)` call also works in `describe` blocks: + +```jsx +describe('when logged in', () => { + afterEach(cleanup) + + it('renders the user', () => { + render() + // ... + }); +}) +``` + +Failing to call `cleanup` when you've called `render` could result in a memory leak and tests which are not "idempotent" (which can lead to difficult to debug errors in your tests). + +The alternative to `cleanup` is balancing every `render` with an `unmount` method call. + ## `fireEvent` ```ts diff --git a/src/__tests__/cleanup.test.js b/src/__tests__/cleanup.test.js new file mode 100644 index 000000000..7ce5bfc46 --- /dev/null +++ b/src/__tests__/cleanup.test.js @@ -0,0 +1,27 @@ +// @flow +/* eslint-disable react/no-multi-comp */ +import React from 'react'; +import { View } from 'react-native'; +import { cleanup, render } from '..'; + +class Test extends React.Component<*> { + componentWillUnmount() { + if (this.props.onUnmount) { + this.props.onUnmount(); + } + } + render() { + return ; + } +} + +test('cleanup', () => { + const fn = jest.fn(); + + render(); + render(); + expect(fn).not.toHaveBeenCalled(); + + cleanup(); + expect(fn).toHaveBeenCalledTimes(2); +}); diff --git a/src/cleanup.js b/src/cleanup.js new file mode 100644 index 000000000..46c084266 --- /dev/null +++ b/src/cleanup.js @@ -0,0 +1,13 @@ +// @flow +let cleanupQueue = new Set(); + +export default function cleanup() { + cleanupQueue.forEach(fn => fn()); + cleanupQueue.clear(); +} + +export function addToCleanupQueue( + fn: (nextElement?: React$Element) => void +) { + cleanupQueue.add(fn); +} diff --git a/src/index.js b/src/index.js index 42547250c..f4263745c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,16 +1,18 @@ // @flow import act from './act'; -import render from './render'; -import shallow from './shallow'; -import flushMicrotasksQueue from './flushMicrotasksQueue'; +import cleanup from './cleanup'; import debug from './debug'; import fireEvent from './fireEvent'; +import flushMicrotasksQueue from './flushMicrotasksQueue'; +import render from './render'; +import shallow from './shallow'; import waitForElement from './waitForElement'; -export { render }; -export { shallow }; -export { flushMicrotasksQueue }; +export { act }; +export { cleanup }; export { debug }; export { fireEvent }; +export { flushMicrotasksQueue }; +export { render }; +export { shallow }; export { waitForElement }; -export { act }; diff --git a/src/render.js b/src/render.js index c560dedc8..cb46f726e 100644 --- a/src/render.js +++ b/src/render.js @@ -2,6 +2,7 @@ import * as React from 'react'; import TestRenderer, { type ReactTestRenderer } from 'react-test-renderer'; // eslint-disable-line import/no-extraneous-dependencies import act from './act'; +import { addToCleanupQueue } from './cleanup'; import { getByAPI } from './helpers/getByAPI'; import { queryByAPI } from './helpers/queryByAPI'; import a11yAPI from './helpers/a11yAPI'; @@ -34,6 +35,8 @@ export default function render( const update = updateWithAct(renderer, wrap); const instance = renderer.root; + addToCleanupQueue(renderer.unmount); + return { ...getByAPI(instance), ...queryByAPI(instance), diff --git a/typings/index.d.ts b/typings/index.d.ts index 66374d7d7..d9d83c4b4 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -135,6 +135,7 @@ export declare const shallow:

( instance: ReactTestInstance | React.ReactElement

) => { output: React.ReactElement

}; export declare const flushMicrotasksQueue: () => Promise; +export declare const cleanup: () => void; export declare const debug: DebugAPI; export declare const fireEvent: FireEventAPI; export declare const waitForElement: WaitForElementFunction;