diff --git a/src/__tests__/renderHook.js b/src/__tests__/renderHook.js
new file mode 100644
index 00000000..fd6b95a4
--- /dev/null
+++ b/src/__tests__/renderHook.js
@@ -0,0 +1,62 @@
+import React from 'react'
+import {renderHook} from '../pure'
+
+test('gives comitted result', () => {
+ const {result} = renderHook(() => {
+ const [state, setState] = React.useState(1)
+
+ React.useEffect(() => {
+ setState(2)
+ }, [])
+
+ return [state, setState]
+ })
+
+ expect(result.current).toEqual([2, expect.any(Function)])
+})
+
+test('allows rerendering', () => {
+ const {result, rerender} = renderHook(
+ ({branch}) => {
+ const [left, setLeft] = React.useState('left')
+ const [right, setRight] = React.useState('right')
+
+ // eslint-disable-next-line jest/no-if
+ switch (branch) {
+ case 'left':
+ return [left, setLeft]
+ case 'right':
+ return [right, setRight]
+
+ default:
+ throw new Error(
+ 'No Props passed. This is a bug in the implementation',
+ )
+ }
+ },
+ {initialProps: {branch: 'left'}},
+ )
+
+ expect(result.current).toEqual(['left', expect.any(Function)])
+
+ rerender({branch: 'right'})
+
+ expect(result.current).toEqual(['right', expect.any(Function)])
+})
+
+test('allows wrapper components', async () => {
+ const Context = React.createContext('default')
+ function Wrapper({children}) {
+ return {children}
+ }
+ const {result} = renderHook(
+ () => {
+ return React.useContext(Context)
+ },
+ {
+ wrapper: Wrapper,
+ },
+ )
+
+ expect(result.current).toEqual('provided')
+})
diff --git a/src/pure.js b/src/pure.js
index 64b761b0..4c416d44 100644
--- a/src/pure.js
+++ b/src/pure.js
@@ -218,8 +218,36 @@ function cleanup() {
mountedContainers.clear()
}
+function renderHook(renderCallback, options = {}) {
+ const {initialProps, wrapper} = options
+ const result = React.createRef()
+
+ function TestComponent({renderCallbackProps}) {
+ const pendingResult = renderCallback(renderCallbackProps)
+
+ React.useEffect(() => {
+ result.current = pendingResult
+ })
+
+ return null
+ }
+
+ const {rerender: baseRerender, unmount} = render(
+ ,
+ {wrapper},
+ )
+
+ function rerender(rerenderCallbackProps) {
+ return baseRerender(
+ ,
+ )
+ }
+
+ return {result, rerender, unmount}
+}
+
// just re-export everything from dom-testing-library
export * from '@testing-library/dom'
-export {render, cleanup, act, fireEvent}
+export {render, renderHook, cleanup, act, fireEvent}
/* eslint func-name-matching:0 */
diff --git a/types/index.d.ts b/types/index.d.ts
index a9bfa279..fda03e5b 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -98,6 +98,52 @@ export function render(
options?: Omit,
): RenderResult
+interface RenderHookResult {
+ /**
+ * Triggers a re-render. The props will be passed to your renderHook callback.
+ */
+ rerender: (props?: Props) => void
+ /**
+ * This is a stable reference to the latest value returned by your renderHook
+ * callback
+ */
+ result: {
+ /**
+ * The value returned by your renderHook callback
+ */
+ current: Result
+ }
+ /**
+ * Unmounts the test component. This is useful for when you need to test
+ * any cleanup your useEffects have.
+ */
+ unmount: () => void
+}
+
+interface RenderHookOptions {
+ /**
+ * The argument passed to the renderHook callback. Can be useful if you plan
+ * to use the rerender utility to change the values passed to your hook.
+ */
+ initialProps?: Props
+ /**
+ * Pass a React Component as the wrapper option to have it rendered around the inner element. This is most useful for creating
+ * reusable custom render functions for common data providers. See setup for examples.
+ *
+ * @see https://testing-library.com/docs/react-testing-library/api/#wrapper
+ */
+ wrapper?: React.JSXElementConstructor<{children: React.ReactElement}>
+}
+
+/**
+ * Allows you to render a hook within a test React component without having to
+ * create that component yourself.
+ */
+export function renderHook(
+ render: (initialProps: Props) => Result,
+ options?: RenderHookOptions,
+): RenderHookResult
+
/**
* Unmounts React trees that were mounted with render.
*/
diff --git a/types/test.tsx b/types/test.tsx
index a8a7c7ae..17ba7012 100644
--- a/types/test.tsx
+++ b/types/test.tsx
@@ -1,5 +1,5 @@
import * as React from 'react'
-import {render, fireEvent, screen, waitFor} from '.'
+import {render, fireEvent, screen, waitFor, renderHook} from '.'
import * as pure from './pure'
export async function testRender() {
@@ -161,6 +161,29 @@ export function testBaseElement() {
)
}
+export function testRenderHook() {
+ const {result, rerender, unmount} = renderHook(() => React.useState(2)[0])
+
+ expectType(result.current)
+
+ rerender()
+
+ unmount()
+}
+
+export function testRenderHookProps() {
+ const {result, rerender, unmount} = renderHook(
+ ({defaultValue}) => React.useState(defaultValue)[0],
+ {initialProps: {defaultValue: 2}},
+ )
+
+ expectType(result.current)
+
+ rerender()
+
+ unmount()
+}
+
/*
eslint
testing-library/prefer-explicit-assert: "off",