React Testing Library is built on top of DOM Testing Library to test React components by querying and interacting with real DOM nodes, avoiding reliance on implementation details.
React Testing Library Cheat Sheet CHEAT SHEET
When it comes to testing React apps manually, we can either choose to render individual component trees in a simplified test environment or run the complete app in a realistic browser environment (end-to-end testing). But for automated tests, React Testing Library (RTL) is recommended for its user-centric approach and maintainability.
reacttestingjavascriptcheatsheet
5
Sections
14
Cards
#Introduction
#Basic level
1. Purpose & Solution
RTL addresses maintainability by focusing on user-visible behavior:
- Tests run in actual DOM nodes.
- Queries mirror user interactions.
data-testidas an escape hatch when needed.- Encourages accessibility.
2. A basic component render test
Component (App.js):
const title = 'Hello, World!';
function App() {
return <div>{title}</div>;
}
export default App;
Test (App.test.js):
import { render } from '@testing-library/react';
import App from './App';
describe('App', () => {
test('renders App component', () => {
render(<App />);
});
});
Add debug to inspect output:
import { render, screen } from '@testing-library/react';
import App from './App';
describe('App', () => {
test('renders App component', () => {
render(<App />);
screen.debug();
});
});
Output in console:
<body>
<div>
<div>Hello, World!</div>
</div>
</body>
3. Why use RTL vs Enzyme?
- Tests based on user interactions, not internal APIs.
- Improves maintainability after refactors.
- Intuitive syntax (
getByText,getByAltText, etc.).
4. Queries in RTL
import { render, screen } from '@testing-library/react';
test('should show login form', () => {
render(<Login />);
const input = screen.getByLabelText('Username');
// events & assertions
});
Single element queries:
getBy*: throws if none foundqueryBy*: returns null if nonefindBy*: async Promise
Multiple elements queries:
getAllBy*: throws if nonequeryAllBy*: returns [] if nonefindAllBy*: async Promise array
5. Component tree testing level
- Test at user interaction level, not per individual child component unless needed.
#Intermediate level
1. Jest vs RTL
- Jest: Test runner & assertion library (
describe,test,expect). - RTL: DOM utilities for React; works within Jest (or other runners).
2. Mocking with MSW
// fetch.test.jsx
import React from 'react';
import { rest } 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(
rest.get('/greeting', (req, res, ctx) =>
res(ctx.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(rest.get('/greeting', (req, res, ctx) => res(ctx.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();
});
3. `render` options
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
test('renders a message', () => {
const table = document.createElement('table');
const { container } = render(<TableBody {...props} />, {
container: document.body.appendChild(table),
baseElement: document.body,
hydrate: true,
legacyRoot: true,
queries: {
/* custom queries */
}
});
expect(container).toBeInTheDocument();
});
4. `renderHook` usage
import { renderHook } from '@testing-library/react';
test('returns logged in user', () => {
const { result, rerender } = renderHook(
({ name } = {}) => useLoggedInUser(name),
{ initialProps: { name: 'Alice' } }
);
expect(result.current).toEqual({ name: 'Alice' });
rerender({ name: 'Bob' });
expect(result.current).toEqual({ name: 'Bob' });
});
#Advanced Level
1. Adding custom queries
const dom = require('@testing-library/dom');
const { queryHelpers, buildQueries } = require('@testing-library/react');
// Override testId attribute
export const queryByTestId = queryHelpers.queryByAttribute.bind(
null,
'data-test-id'
);
export const queryAllByTestId = queryHelpers.queryAllByAttribute.bind(
null,
'data-test-id'
);
export function getAllByTestId(container, id, ...rest) {
const els = queryAllByTestId(container, id, ...rest);
if (!els.length)
throw queryHelpers.getElementError(
`No element with [data-test-id="${id}"]`,
container
);
return els;
}
export function getByTestId(container, id, ...rest) {
const els = getAllByTestId(container, id, ...rest);
if (els.length > 1)
throw queryHelpers.getElementError(
`Multiple elements with [data-test-id="${id}"]`,
container
);
return els[0];
}
Or using buildQueries:
const queryAllByDataCy = (...args) =>
queryHelpers.queryAllByAttribute('data-cy', ...args);
const [
queryByDataCy,
getAllByDataCy,
getByDataCy,
findAllByDataCy,
findByDataCy
] = buildQueries(
queryAllByDataCy,
(c, v) => `Found multiple elements with data-cy="${v}"`,
(c, v) => `Unable to find element with data-cy="${v}"`
);
2. Skipping auto cleanup
- Via CLI:
cross-env RTL_SKIP_AUTO_CLEANUP=true jest - Or add to Jest
setupFiles:import '@testing-library/react/dont-cleanup-after-each';
3. Migrating from Enzyme
- Install RTL & jest-dom.
- Replace
shallow/mountwithrender+screen. - Migrate tests incrementally.
4. Querying within elements
import { render, within } from '@testing-library/react';
const { getByText } = render(<MyComponent />);
const section = getByText('messages');
const hello = within(section).getByText('hello');
5. Integration testing
import { render, cleanup, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import nock from 'nock';
import App from '../App';
const REPOS = [{ name: 'repo1', description: '...' }];
beforeAll(() =>
nock('https://api.github.com')
.persist()
.get('/users/alice/repos')
.reply(200, REPOS)
);
afterEach(cleanup);
test('user sees public repos', async () => {
render(<App />);
userEvent.type(screen.getByPlaceholderText('Enter username'), 'alice');
userEvent.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() =>
REPOS.forEach((r) => expect(screen.getByText(r.name)).toBeInTheDocument())
);
expect(screen.queryByText('Loading...')).toBeNull();
});
#Conclusion
This comprehensive cheat sheet covers basic to advanced RTL usage—rendering, querying, mocking, custom queries, and integration tests—to help you write robust, maintainable tests.