diff --git a/src/__tests__/renderHook.js b/src/__tests__/renderHook.js
new file mode 100644
index 0000000..8777d43
--- /dev/null
+++ b/src/__tests__/renderHook.js
@@ -0,0 +1,62 @@
+import { createContext, h } from 'preact'
+import { useState, useContext, useEffect } from 'preact/hooks'
+import { renderHook } from '../pure'
+
+test('gives comitted result', () => {
+ const { result } = renderHook(() => {
+ const [state, setState] = useState(1)
+
+ 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] = useState('left')
+ const [right, setRight] = useState('right')
+
+ 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 = createContext('default')
+ function Wrapper ({ children }) {
+ return {children}
+ }
+ const { result } = renderHook(
+ () => {
+ return useContext(Context)
+ },
+ {
+ wrapper: Wrapper
+ }
+ )
+
+ expect(result.current).toEqual('provided')
+})
diff --git a/src/pure.js b/src/pure.js
index 8f594aa..e2d8468 100644
--- a/src/pure.js
+++ b/src/pure.js
@@ -1,5 +1,6 @@
import { getQueriesForElement, prettyDOM, configure as configureDTL } from '@testing-library/dom'
-import { h, hydrate as preactHydrate, render as preactRender } from 'preact'
+import { h, hydrate as preactHydrate, render as preactRender, createRef } from 'preact'
+import { useEffect } from 'preact/hooks'
import { act } from 'preact/test-utils'
import { fireEvent } from './fire-event'
@@ -107,7 +108,35 @@ function cleanup () {
mountedContainers.forEach(cleanupAtContainer)
}
+function renderHook (renderCallback, options) {
+ const { initialProps, wrapper } = (options || {})
+ const result = createRef()
+
+ function TestComponent ({ renderCallbackProps }) {
+ const pendingResult = renderCallback(renderCallbackProps)
+
+ useEffect(() => {
+ result.current = pendingResult
+ })
+
+ return null
+ }
+
+ const { rerender: baseRerender, unmount } = render(
+ ,
+ { wrapper }
+ )
+
+ function rerender (rerenderCallbackProps) {
+ return baseRerender(
+
+ )
+ }
+
+ return { result, rerender, unmount }
+}
+
// eslint-disable-next-line import/export
export * from '@testing-library/dom'
// eslint-disable-next-line import/export
-export { render, cleanup, act, fireEvent }
+export { render, cleanup, act, fireEvent, renderHook }
diff --git a/types/index.d.ts b/types/index.d.ts
index 4a04c35..2480d09 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -1,6 +1,6 @@
import { queries, Queries, BoundFunction } from '@testing-library/dom'
import { act as preactAct } from 'preact/test-utils'
-import { ComponentChild } from 'preact'
+import { ComponentChild, ComponentType, Element } from 'preact'
export * from '@testing-library/dom'
@@ -46,3 +46,49 @@ export function cleanup(): void
export const act: typeof preactAct extends undefined
? (callback: () => void) => void
: typeof preactAct
+
+export 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
+}
+
+export 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?: ComponentType<{ children: Element }>
+}
+
+/**
+ * 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