diff --git a/README.md b/README.md index fe43640c..16aba65d 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ practices.

- [Example](#example) - [Installation](#installation) - [Examples](#examples) +- [Hooks](#hooks) - [Other Solutions](#other-solutions) - [Guiding Principles](#guiding-principles) - [Contributors](#contributors) @@ -162,12 +163,15 @@ Some included are: - [`react-redux`](https://github.com/kentcdodds/react-testing-library/blob/master/examples/__tests__/react-redux.js) - [`react-router`](https://github.com/kentcdodds/react-testing-library/blob/master/examples/__tests__/react-router.js) - [`react-context`](https://github.com/kentcdodds/react-testing-library/blob/master/examples/__tests__/react-context.js) -- [`react-hooks`](https://github.com/kentcdodds/react-testing-library/blob/master/examples/__tests__/react-hooks.js) - - Use react-testing-library to test a custom React Hook. You can also find react-testing-library examples at [react-testing-examples.com](https://react-testing-examples.com/jest-rtl/). +## Hooks + +If you are interested in testing a custom hook, check out +[react-hooks-testing-library][react-hooks-testing-library] + ## Other Solutions In preparing this project, @@ -296,5 +300,6 @@ Links: [good-first-issue]: https://github.com/kentcdodds/react-testing-library/issues?utf8=✓&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3A"good+first+issue"+ [reactiflux]: https://www.reactiflux.com/ [stackoverflow]: https://stackoverflow.com/questions/tagged/react-testing-library +[react-hooks-testing-library]: https://github.com/mpeyper/react-hooks-testing-library diff --git a/examples/__tests__/react-hooks.js b/examples/__tests__/react-hooks.js deleted file mode 100644 index fe616eb7..00000000 --- a/examples/__tests__/react-hooks.js +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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. - */ -import {testHook, act, cleanup} from 'react-testing-library' - -import {useCounter, useDocumentTitle, useCall} from '../react-hooks' - -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) - }) -}) - -// using unmount function to check useEffect behavior when unmounting -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') - }) -}) - -// using rerender function to test calling useEffect multiple times -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) - }) -}) diff --git a/src/__tests__/test-hook.js b/src/__tests__/test-hook.js deleted file mode 100644 index 4b54bfb8..00000000 --- a/src/__tests__/test-hook.js +++ /dev/null @@ -1,77 +0,0 @@ -import React, {useState, useEffect} from 'react' -import 'jest-dom/extend-expect' -import {testHook, cleanup, act} from '../' - -afterEach(cleanup) - -test('testHook calls the callback', () => { - const spy = jest.fn() - testHook(spy) - expect(spy).toHaveBeenCalledTimes(1) -}) -test('confirm we can safely call a React Hook from within the callback', () => { - testHook(() => useState()) -}) -test('returns a function to unmount component', () => { - let isMounted - const {unmount} = testHook(() => { - useEffect(() => { - isMounted = true - return () => { - isMounted = false - } - }) - }) - expect(isMounted).toBe(true) - unmount() - expect(isMounted).toBe(false) -}) -test('returns a function to rerender component', () => { - let renderCount = 0 - const {rerender} = testHook(() => { - useEffect(() => { - renderCount++ - }) - }) - - expect(renderCount).toBe(1) - rerender() - expect(renderCount).toBe(2) -}) -test('accepts wrapper option to wrap rendered hook with', () => { - const ctxA = React.createContext() - const ctxB = React.createContext() - const useHook = () => { - return React.useContext(ctxA) * React.useContext(ctxB) - } - let actual - testHook( - () => { - actual = useHook() - }, - { - // eslint-disable-next-line react/display-name - wrapper: props => ( - - - - ), - }, - ) - expect(actual).toBe(12) -}) -test('returns result ref with latest result from hook execution', () => { - 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} - } - - const {result} = testHook(useCounter) - expect(result.current.count).toBe(0) - act(() => { - result.current.increment() - }) - expect(result.current.count).toBe(1) -}) diff --git a/src/index.js b/src/index.js index 70bf83c2..d2661920 100644 --- a/src/index.js +++ b/src/index.js @@ -31,7 +31,6 @@ function render( // they're passing us a custom container or not. mountedContainers.add(container) - const wrapUiIfNeeded = innerElement => WrapperComponent ? React.createElement(WrapperComponent, null, innerElement) @@ -72,33 +71,6 @@ function render( } } -function TestHook({callback, children}) { - children(callback()) - return null -} - -function testHook(callback, options = {}) { - const result = { - current: null, - } - const toRender = () => ( - - {res => { - result.current = res - }} - - ) - - const {unmount, rerender: rerenderComponent} = render(toRender(), options) - return { - result, - unmount, - rerender: () => { - rerenderComponent(toRender(), options) - }, - } -} - function cleanup() { mountedContainers.forEach(cleanupAtContainer) } @@ -159,6 +131,6 @@ fireEvent.select = (node, init) => { // just re-export everything from dom-testing-library export * from 'dom-testing-library' -export {render, testHook, cleanup, fireEvent, act} +export {render, cleanup, fireEvent, act} /* eslint func-name-matching:0 */ diff --git a/typings/index.d.ts b/typings/index.d.ts index af09f778..06eaeeaf 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -19,12 +19,6 @@ export type RenderResult = { asFragment: () => DocumentFragment } & {[P in keyof Q]: BoundFunction} -export type HookResult = { - result: React.MutableRefObject - rerender: () => void - unmount: () => boolean -} - export interface RenderOptions { container?: HTMLElement baseElement?: HTMLElement @@ -33,8 +27,6 @@ export interface RenderOptions { wrapper?: React.ComponentType } -export type HookOptions = RenderOptions - type Omit = Pick> /** @@ -49,14 +41,6 @@ export function render( options: RenderOptions, ): RenderResult -/** - * Renders a test component that calls back to the test. - */ -export function testHook( - callback: () => T, - options?: Partial, -): HookResult - /** * Unmounts React trees that were mounted with render. */