MENU
React Testing Library
Many modern project scaffolds come with React Testing Library pre-configured (Create React App, now deprecated, did this out of the box), or it can easily be added to any setup, such as Vite, with a couple of dependencies. Rather than dealing with instances of rendered React components, React Testing Library works with actual DOM nodes, resembling the way your software is actually used.
// fetch.js
import {useState, useReducer} from 'react'
import axios from 'axios'
const initialState = {
error: null,
greeting: null,
}
function greetingReducer(state, action) {
switch (action.type) {
case 'SUCCESS': {
return {
error: null,
greeting: action.greeting,
}
}
case 'ERROR': {
return {
error: action.error,
greeting: null,
}
}
default: {
return state
}
}
}
export default function Fetch({url}) {
const [{error, greeting}, dispatch] = useReducer(
greetingReducer,
initialState,
)
const [buttonClicked, setButtonClicked] = useState(false)
const fetchGreeting = async url =>
axios
.get(url)
.then(response => {
const {data} = response
const {greeting} = data
dispatch({type: 'SUCCESS', greeting})
setButtonClicked(true)
})
.catch(error => {
dispatch({type: 'ERROR', error})
})
const buttonText = buttonClicked ? 'Ok' : 'Load Greeting'
return (
<div>
<button onClick={() => fetchGreeting(url)} disabled={buttonClicked}>
{buttonText}
</button>
{greeting && <h1>{greeting}</h1>}
{error && <p role="alert">Oops, failed to fetch!</p>}
</div>
)
}// __tests__/fetch.test.js
import {http, HttpResponse} from 'msw'
import {setupServer} from 'msw/node'
import {render, fireEvent, waitFor, screen} from '@testing-library/react'
import '@testing-library/jest-dom'
import Fetch from '../fetch'
const server = setupServer(
http.get('/greeting', () => {
return HttpResponse.json({greeting: 'hello there'})
}),
)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
test('loads and displays greeting', async () => {
render(<Fetch url="/greeting" />)
fireEvent.click(screen.getByText('Load Greeting'))
await waitFor(() => screen.getByRole('heading'))
expect(screen.getByRole('heading')).toHaveTextContent('hello there')
expect(screen.getByRole('button')).toBeDisabled()
})
test('handles server error', async () => {
server.use(
http.get('/greeting', () => {
return new HttpResponse(null, {status: 500})
}),
)
render(<Fetch url="/greeting" />)
fireEvent.click(screen.getByText('Load Greeting'))
await waitFor(() => screen.getByRole('alert'))
expect(screen.getByRole('alert')).toHaveTextContent('Oops, failed to fetch!')
expect(screen.getByRole('button')).not.toBeDisabled()
})Other APIs: RTL's queries all follow the pattern {get|find|query}[All]By{Role|LabelText|PlaceholderText|Text|DisplayValue|AltText|Title|TestId} — for example, getByRole, findAllByText, queryByTestId, and so on.
| No Match | 1 Match | 1+ Match | Await? | |
|---|---|---|---|---|
| getBy | Throw | Return | Throw | No |
| findBy | Throw | Return | Throw | Yes |
| queryBy | Null | Return | Throw | No |
| getAllBy | Throw | Array | Array | No |
| findAllBy | Throw | Array | Array | Yes |
| queryAllBy | [] | Array | array | No |
// Matching a string:
getByText('Hello World') // full string match
getByText('llo Worl', {exact: false}) // substring match
getByText('hello world', {exact: false}) // ignore case
// Matching a regex:
getByText(/World/) // substring match
getByText(/world/i) // substring match, ignore case
getByText(/^hello world$/i) // full string match, ignore case
getByText(/Hello W?oRlD/i) // advanced regex
// Matching with a custom function:
getByText((content, element) => content.startsWith('Hello'))waitForElementToBeRemoved (Promise) retries the function until it no longer returns a DOM node.
act is a wrapper around the act testing helper (exported from react-dom/test-utils historically, and from the react package itself as of React 19). React Testing Library wraps render and fireEvent in a call to act already so most cases should not require using it manually.
within takes a node and returns an object with all the queries bound to the node (used to return the queries from React Testing Library's render method). Eg.: within(node).getByText("hello")
configure changes global options. Eg.: configure({testIdAttribute: 'my-data-test-id'})
cleanup clears the DOM; it's used with afterEach to reset the DOM between tests. As of React Testing Library v9, this happens automatically after each test as long as your test framework exposes a global afterEach (both Jest and Vitest do), so manual afterEach(cleanup) calls are rarely needed anymore.
render renders into a container which is appended to document.body. (options: container, baseElement, hydrate, wrapper, queries)
import {render} from '@testing-library/react'
import '@testing-library/jest-dom'
test('renders a message', () => {
const {container, getByText} = render(<Greeting />)
expect(getByText('Hello, World!')).toBeInTheDocument()
expect(container.firstChild).toMatchInlineSnapshot(`
<h1>Hello, World!</h1>
`)
})// Example, a function to traverse table contents
import * as tableQueries from 'my-table-query-library'
import {queries} from '@testing-library/react'
const {getByRowColumn, getByText} = render(<MyTable />, {
queries: {...queries, ...tableQueries},
})render Result: ...queries, container, baseElement, debug, rerender, unmount, asFragment
import {render} from '@testing-library/react'
const {container, unmount} = render(<Login />)
unmount()
// your component has been unmounted and now: container.innerHTML === ''