Skip to content

Updated react hooks example page to match react-hooks-testing-library's documentation #64

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

Open
wants to merge 5 commits into
base: hooks
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 33 additions & 164 deletions docs/example-react-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Copy link
Contributor

@rawat07102 rawat07102 Oct 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should replace this description with react-hooks-testing-library "The solution" section with an updated link for react-hooks-testing-library.


> **Note**
>
Expand All @@ -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)
})
```
68 changes: 1 addition & 67 deletions docs/react-testing-library/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
16 changes: 0 additions & 16 deletions website/blog/2019-02-06-react-hooks.md

This file was deleted.