diff --git a/docs/example-react-hooks.md b/docs/example-react-hooks.md index cbc14c73a..2b71116ab 100644 --- a/docs/example-react-hooks.md +++ b/docs/example-react-hooks.md @@ -3,9 +3,7 @@ id: example-react-hooks title: React Hooks --- -`react-testing-library` provides the -[`testHook`](/docs/react-testing-library/api#testhook) utility to test custom -hooks. +[`react-hooks-testing-library`](https://github.com/mpeyper/react-hooks-testing-library) is built on top of [`react-testing-library`](/react) to create a simple test harness for [React hooks](https://reactjs.org/docs/hooks-intro.html) that handles running them within the body of a function component, as well as providing various useful utility functions for updating the inputs and retrieving the outputs of your [custom hook](https://reactjs.org/docs/hooks-custom.html). > **Note** > @@ -14,189 +12,60 @@ hooks. > hooks. Typically those are better tested by testing the component that is > using it. -## Using `result` - -Testing the last returned value of a hook using the `result` ref - -```jsx -function useCounter({ initialCount = 0, step = 1 } = {}) { - const [count, setCount] = React.useState(initialCount) - const increment = () => setCount(c => c + step) - const decrement = () => setCount(c => c - step) - return { count, increment, decrement } -} ``` - -```jsx -test('returns result ref with latest result from hook execution', () => { - const { result } = testHook(useCounter) - expect(result.current.count).toBe(0) - act(() => { - result.current.increment() - }) - expect(result.current.count).toBe(1) -}) +npm install --save-dev react-hooks-testing-library ``` -## State - -Testing a hook that provides state - -```jsx +```js +// useCounter.js import { useState } from 'react' -export function useCounter({ initialCount = 0, step = 1 } = {}) { +function useCounter(initialCount = 0) { const [count, setCount] = useState(initialCount) - const increment = () => setCount(c => c + step) - const decrement = () => setCount(c => c - step) - return { count, increment, decrement } + + const incrementBy = useCallback((n) => setCount(count + n), [count]) + const decrementBy = useCallback((n) => setCount(count - n), [count]) + + return { count, incrementBy, decrementBy } } + +export default useCounter ``` -```jsx -import { testHook, act, cleanup } from 'react-testing-library' +```js +// useCounter.test.js +import { renderHook, cleanup, act } from 'react-hooks-testing-library' +import useCounter from './useCounter' + afterEach(cleanup) -describe('useCounter', () => { - test('accepts default initial values', () => { - let count - testHook(() => ({ count } = useCounter())) - - expect(count).toBe(0) - }) - - test('accepts a default initial value for `count`', () => { - let count - testHook(() => ({ count } = useCounter({}))) - - expect(count).toBe(0) - }) - - test('provides an `increment` function', () => { - let count, increment - testHook(() => ({ count, increment } = useCounter({ step: 2 }))) - - expect(count).toBe(0) - act(() => { - increment() - }) - expect(count).toBe(2) - }) - - test('provides an `decrement` function', () => { - let count, decrement - testHook(() => ({ count, decrement } = useCounter({ step: 2 }))) - - expect(count).toBe(0) - act(() => { - decrement() - }) - expect(count).toBe(-2) - }) - - test('accepts a default initial value for `step`', () => { - let count, increment - testHook(() => ({ count, increment } = useCounter({}))) - - expect(count).toBe(0) - act(() => { - increment() - }) - expect(count).toBe(1) - }) +test('should create counter', () => { + const { result } = renderHook(() => useCounter()) + + expect(result.current.count).toBe(0) }) -``` -## Unmount Side-Effects +test('should increment counter', () => { + const { result } = renderHook(() => useCounter()) -Using the `unmount` function to check useEffect behavior when unmounting + act(() => result.current.incrementBy(1)) -```jsx -import { useState, useEffect } from 'react' + expect(result.current.count).toBe(1) -export function useDocumentTitle(title) { - const [originalTitle, setOriginalTitle] = useState(document.title) - useEffect(() => { - setOriginalTitle(document.title) - document.title = title - return () => { - document.title = originalTitle - } - }, [title]) -} -``` + act(() => result.current.incrementBy(2)) -```jsx -describe('useDocumentTitle', () => { - test('sets a title', () => { - document.title = 'original title' - testHook(() => { - useDocumentTitle('modified title') - }) - - expect(document.title).toBe('modified title') - }) - - test('returns to original title when component is unmounted', () => { - document.title = 'original title' - const { unmount } = testHook(() => { - useDocumentTitle('modified title') - }) - - unmount() - expect(document.title).toBe('original title') - }) + expect(result.current.count).toBe(3) }) -``` -## Rerender Side-Effects +test('should decrement counter', () => { + const { result } = renderHook(() => useCounter()) -Using the `rerender` function to test calling useEffect multiple times + act(() => result.current.decrementBy(1)) -```jsx -import { useEffect } from 'react' + expect(result.current.count).toBe(-1) -export function useCall(callback, deps) { - useEffect(() => { - callback() - }, deps) -} -``` + act(() => result.current.decrementBy(2)) -```jsx -describe('useCall', () => { - test('calls once on render', () => { - const spy = jest.fn() - testHook(() => { - useCall(spy, []) - }) - expect(spy).toHaveBeenCalledTimes(1) - }) - - test('calls again if deps change', () => { - let deps = [false] - const spy = jest.fn() - const { rerender } = testHook(() => { - useCall(spy, deps) - }) - expect(spy).toHaveBeenCalledTimes(1) - - deps = [true] - rerender() - expect(spy).toHaveBeenCalledTimes(2) - }) - - test('does not call again if deps are the same', () => { - let deps = [false] - const spy = jest.fn() - const { rerender } = testHook(() => { - useCall(spy, deps) - }) - expect(spy).toHaveBeenCalledTimes(1) - - deps = [false] - rerender() - expect(spy).toHaveBeenCalledTimes(1) - }) + expect(result.current.count).toBe(-3) }) ``` diff --git a/docs/react-testing-library/api.md b/docs/react-testing-library/api.md index 82d80a747..303d43404 100644 --- a/docs/react-testing-library/api.md +++ b/docs/react-testing-library/api.md @@ -253,70 +253,4 @@ set up your framework. This is a light wrapper around the [`react-dom/test-utils` `act` function](https://reactjs.org/docs/test-utils.html#act). All it does is forward all arguments to the act function if your version of -react supports `act`. - -## `testHook` - -`testHook` is a utility to test custom hooks. It is designed to help test -reusable hooks in isolation. - -You should also write integration tests for components using custom hooks, and -one-off hooks should be tested as part of the component instead. - -**Usage** - -```jsx -import { testHook } from 'react-testing-libary' - -testHook(hook[, renderOptions]) -``` - -**Arguments** - -- `hook` customHook to test -- `renderOptions` options object to pass to the underlying `render`. See - [render options](#render-options). This is mostly useful for wrapping the hook - with a context provider. - -**Returns** - -```jsx -const { rerender, unmount, result } = testHook(hook) -``` - -- `rerender` Call this function to render the wrapper again, i.e., to test that - the hook handles props changes -- `unmount` Call this to unmount the component, i.e., to test side-effects and - cleanup behavior -- `result` An object that acts like a React ref with a `current` property - pointing to the last value the hook returned. For example: - `expect(result.current.count).toBe(0)` - -**Example** - -```jsx -// Example custom hook -function useCounter({ initialCount = 0, step = 1 } = {}) { - const [count, setCount] = React.useState(initialCount) - const increment = () => setCount(c => c + step) - const decrement = () => setCount(c => c - step) - return { count, increment, decrement } -} -``` - -```jsx -// Test using the `result` ref -test('returns result ref with latest result from hook execution', () => { - const { result } = testHook(useCounter) - - expect(result.current.count).toBe(0) - act(() => result.current.increment()) - expect(result.current.count).toBe(1) -}) -``` - -**More** - -- [More Examples](/docs/example-react-hooks) -- [Tests](https://github.com/kentcdodds/react-testing-library/blob/master/src/__tests__/test-hook.js) -- [Types](https://github.com/kentcdodds/react-testing-library/blob/master/typings/index.d.ts) +react supports `act`. \ No newline at end of file diff --git a/website/blog/2019-02-06-react-hooks.md b/website/blog/2019-02-06-react-hooks.md deleted file mode 100755 index 770920879..000000000 --- a/website/blog/2019-02-06-react-hooks.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: React Hooks Are Supported -author: Alex Krolick -authorURL: http://github.com/alexkrolick ---- - -[Hooks have been released in React 16.8](https://reactjs.org/blog/2019/02/06/react-v16.8.0.html#testing-hooks) -and they are supported out of the box by `react-testing-library`! - -Because `react-testing-library` only uses the external interface of your React -components, hooks work right away! If you rewrite a class component with hooks -your tests should still pass. - -For unit testing custom hooks, we've also added a `testHook` utility. Check out -the [docs for `testHook`](/docs/react-testing-library/api#testhook). Thanks to -[@donavon](https://github.com/donavon) for the PR.