diff --git a/docs/docs/testing.md b/docs/docs/testing.md index 63064466181d..c9aa7a5e4573 100644 --- a/docs/docs/testing.md +++ b/docs/docs/testing.md @@ -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 }) => ( - - {children} - - ) -}) -``` -::: - -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. @@ -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 }) => ( + + {children} + + ) +}) +``` +::: + +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!