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

[Docs]: Move hooks section after components section in testing reference #6813

Closed
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
182 changes: 91 additions & 91 deletions docs/docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,97 +288,6 @@ render(
)
```

## Testing Custom Hooks

Custom hooks are a great way to encapsulate non-presentational code.
To test custom hooks, we'll use the `renderHook` function from `@redwoodjs/testing/web`.

:::info
Note that Redwood's `renderHook` function is based on React Testing Library's. The only difference is that Redwood's wraps everything with mock providers for the various providers in Redwood, such as auth, the GraphQL client, the router, etc.

If you were to use React Testing Library's `renderHook` function, you'd need to provide your own wrapper function. In this case you probably want to compose the mock providers from `@redwoodjs/testing/web`:

```jsx
import { renderHook, MockProviders } from '@redwoodjs/testing/web'

// ...

renderHook(() => myCustomHook(), {
wrapper: ({ children }) => (
<MockProviders>
<MyCustomProvider>{children}</MyCustomProvider>
</MockProviders>
)
})
```
:::

To use `renderHook`:
1. Call your custom hook from an inline function passed to `renderHook`. For example:
```js
const { result } = renderHook(() => useAccumulator(0))
```
2. `renderHook` will return an object with the following properties:
- `result`: holds the return value of the hook in its `current` property (so `result.current`). Think of `result` as a `ref` for the most recently returned value
- `rerender`: a function to render the previously rendered hook with new props

Let's go through an example. Given the following custom hook:

```js title="web/src/hooks/useAccumulator/useAccumulator.js"
const useAccumulator = (initialValue) => {
const [total, setTotal] = useState(initialValue)

const add = (value) => {
const newTotal = total + value
setTotal(newTotal)
return newTotal
}

return { total, add }
}
```

The test could look as follows:

```js title="web/src/hooks/useAccumulator/useAccumulator.test.js"
import { renderHook } from '@redwoodjs/testing/web'
import { useAccumulator } from './useAccumulator'

describe('useAccumulator hook example in docs', () => {
it('has the correct initial state', () => {
const { result } = renderHook(() => useAccumulator(42))
expect(result.current.total).toBe(42)
})

it('adds a value', () => {
const { result } = renderHook(() => useAccumulator(1))
result.current.add(5)
expect(result.current.total).toBe(6)
})

it('adds multiple values', () => {
const { result } = renderHook(() => useAccumulator(0))
result.current.add(5)
result.current.add(10)
expect(result.current.total).toBe(15)
})

it('re-initializes the accumulator if passed a new initializing value', () => {
const { result, rerender } = renderHook(
(initialValue) => useAccumulator(initialValue),
{
initialProps: 0,
}
)
result.current.add(5)
rerender(99)
expect(result.current.total).toBe(99)
})
})
```

While `renderHook` lets you test a custom hook directly, there are cases where encapsulating the custom hook in a component is more robust. See https://kentcdodds.com/blog/how-to-test-custom-react-hooks.

### Queries

In most cases you will want to exclude the design elements and structure of your components from your test. Then you're free to redesign the component all you want without also having to make the same changes to your test suite. Let's look at some of the functions that React Testing Library provides (they call them "[queries](https://testing-library.com/docs/queries/about/)") that let you check for *parts* of the rendered component, rather than a full string match.
Expand Down Expand Up @@ -848,6 +757,97 @@ Some schools of thought say you should keep your test files flat (that is, no ne

> For what it's worth, your humble author endorses the flat tests style.

## Testing Custom Hooks

Custom hooks are a great way to encapsulate non-presentational code.
To test custom hooks, we'll use the `renderHook` function from `@redwoodjs/testing/web`.

:::info
Note that Redwood's `renderHook` function is based on React Testing Library's. The only difference is that Redwood's wraps everything with mock providers for the various providers in Redwood, such as auth, the GraphQL client, the router, etc.

If you were to use React Testing Library's `renderHook` function, you'd need to provide your own wrapper function. In this case you probably want to compose the mock providers from `@redwoodjs/testing/web`:

```jsx
import { renderHook, MockProviders } from '@redwoodjs/testing/web'

// ...

renderHook(() => myCustomHook(), {
wrapper: ({ children }) => (
<MockProviders>
<MyCustomProvider>{children}</MyCustomProvider>
</MockProviders>
)
})
```
:::

To use `renderHook`:
1. Call your custom hook from an inline function passed to `renderHook`. For example:
```js
const { result } = renderHook(() => useAccumulator(0))
```
2. `renderHook` will return an object with the following properties:
- `result`: holds the return value of the hook in its `current` property (so `result.current`). Think of `result` as a `ref` for the most recently returned value
- `rerender`: a function to render the previously rendered hook with new props

Let's go through an example. Given the following custom hook:

```js title="web/src/hooks/useAccumulator/useAccumulator.js"
const useAccumulator = (initialValue) => {
const [total, setTotal] = useState(initialValue)

const add = (value) => {
const newTotal = total + value
setTotal(newTotal)
return newTotal
}

return { total, add }
}
```

The test could look as follows:

```js title="web/src/hooks/useAccumulator/useAccumulator.test.js"
import { renderHook } from '@redwoodjs/testing/web'
import { useAccumulator } from './useAccumulator'

describe('useAccumulator hook example in docs', () => {
it('has the correct initial state', () => {
const { result } = renderHook(() => useAccumulator(42))
expect(result.current.total).toBe(42)
})

it('adds a value', () => {
const { result } = renderHook(() => useAccumulator(1))
result.current.add(5)
expect(result.current.total).toBe(6)
})

it('adds multiple values', () => {
const { result } = renderHook(() => useAccumulator(0))
result.current.add(5)
result.current.add(10)
expect(result.current.total).toBe(15)
})

it('re-initializes the accumulator if passed a new initializing value', () => {
const { result, rerender } = renderHook(
(initialValue) => useAccumulator(initialValue),
{
initialProps: 0,
}
)
result.current.add(5)
rerender(99)
expect(result.current.total).toBe(99)
})
})
```

While `renderHook` lets you test a custom hook directly, there are cases where encapsulating the custom hook in a component is more robust. See https://kentcdodds.com/blog/how-to-test-custom-react-hooks.

## Testing Pages & Layouts

Pages and Layouts are just regular components so all the same techniques apply!
Expand Down