-
Notifications
You must be signed in to change notification settings - Fork 723
Hooks #32
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
Hooks #32
Changes from 4 commits
40cacc6
4b5b678
0869823
01dd2d7
3574018
c56fffe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
--- | ||
id: example-react-hooks | ||
title: React Hooks | ||
--- | ||
|
||
`react-testing-library` provides the | ||
[`testHook`](/docs/react-testing-library/api#testhook) utility to test custom | ||
hooks. | ||
|
||
> **Note** | ||
> | ||
> This is the recommended way to test reusable custom react hooks. It is not | ||
> however recommended to use the testHook utility to test single-use custom | ||
> 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) | ||
}) | ||
``` | ||
|
||
## State | ||
|
||
Testing a hook that provides state | ||
|
||
```jsx | ||
import { useState } from 'react' | ||
|
||
export function useCounter({ initialCount = 0, step = 1 } = {}) { | ||
const [count, setCount] = useState(initialCount) | ||
const increment = () => setCount(c => c + step) | ||
const decrement = () => setCount(c => c - step) | ||
return { count, increment, decrement } | ||
} | ||
``` | ||
|
||
```jsx | ||
import { testHook, act, cleanup } from 'react-testing-library' | ||
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) | ||
}) | ||
}) | ||
``` | ||
|
||
## Unmount Side-Effects | ||
|
||
Using the `unmount` function to check useEffect behavior when unmounting | ||
|
||
```jsx | ||
import { useState, useEffect } from 'react' | ||
|
||
export function useDocumentTitle(title) { | ||
const [originalTitle, setOriginalTitle] = useState(document.title) | ||
useEffect(() => { | ||
setOriginalTitle(document.title) | ||
document.title = title | ||
return () => { | ||
document.title = originalTitle | ||
} | ||
}, [title]) | ||
} | ||
``` | ||
|
||
```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') | ||
}) | ||
}) | ||
``` | ||
|
||
## Rerender Side-Effects | ||
|
||
Using the `rerender` function to test calling useEffect multiple times | ||
|
||
```jsx | ||
import { useEffect } from 'react' | ||
|
||
export function useCall(callback, deps) { | ||
useEffect(() => { | ||
callback() | ||
}, deps) | ||
} | ||
``` | ||
|
||
```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) | ||
}) | ||
}) | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
--- | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a little FYI blog post about hooks, since they are new in the docs |
||
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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -95,7 +95,7 @@ const siteConfig = { | |
/* Colors for website */ | ||
colors: { | ||
primaryColor: '#292422', | ||
secondaryColor: '#3344bb', | ||
secondaryColor: '#2468e5', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unrelated, but the links were hard to read. This is a lighter color so they stand out from the text. |
||
}, | ||
|
||
// Add custom scripts here that would be placed in <script> tags. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pulled in these examples from the rtl repo