Prioritize in this order: getByRole
> getByText
> getByTestId
Query Type | Method | When to Use | Example |
---|---|---|---|
Single Element | getBy... |
Element must exist | screen.getByText('Submit') |
queryBy... |
Check for absence (use with .toBeNull() ) |
screen.queryByText('Error') |
|
findBy... |
Async elements (use with await ) |
await screen.findByText('Success') |
|
Multiple Elements | getAllBy... |
1+ elements must exist | screen.getAllByRole('button') |
queryAllBy... |
Check for 0+ elements | screen.queryAllByTestId('item') |
|
findAllBy... |
Async multiple elements (use with await ) |
await screen.findAllByAltText('Avatar') |
// Most useful queries:
screen.getByRole('button', { name: /submit/i })
screen.getByText(/welcome back/i)
screen.getByLabelText(/email address/i)
screen.getByPlaceholderText('Enter password')
import { describe, it, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Component from './Component';
describe('ComponentName', () => {
it('does something', async () => {
// 1. Setup
const mockFn = vi.fn();
render(<Component onClick={mockFn} />);
// 2. Find elements
const button = screen.getByRole('button', { name: /click me/i });
// 3. Simulate interactions
await userEvent.click(button);
// 4. Assertions
expect(mockFn).toHaveBeenCalled();
expect(screen.getByText('Success')).toBeVisible();
});
});
Always use await
with userEvent!
Method | Use Case | Example |
---|---|---|
.click() |
Click buttons/links | await userEvent.click(submitButton) |
.type() |
Type into text fields | await userEvent.type(input, 'Hello{enter}') |
.keyboard() |
Special key presses | await userEvent.keyboard('{Tab}') |
.hover() |
Test hover effects | await userEvent.hover(menuItem) |
.selectOptions() |
Choose dropdown options | await userEvent.selectOptions(select, 'Option1') |
.upload() |
File uploads | await userEvent.upload(fileInput, file) |
Matcher | Checks | Example |
---|---|---|
.toBeInTheDocument() |
Element exists in DOM | expect(element).toBeInTheDocument() |
.toBeVisible() |
Element is not hidden | expect(modal).toBeVisible() |
.toHaveTextContent() |
Text content matches | expect(header).toHaveTextContent('Hello') |
.toHaveClass() |
CSS class exists | expect(button).toHaveClass('active') |
.toHaveBeenCalled() |
Function was called | expect(mockFn).toHaveBeenCalled() |
.toHaveValue() |
Input value matches | expect(input).toHaveValue('test') |
.toHaveAttribute() |
HTML attribute exists | expect(link).toHaveAttribute('href', '#') |
Scenario | Recommended Query |
---|---|
Element must be present | getBy... / getAllBy... |
Verify element is missing | queryBy... |
Async elements (API response) | findBy... (with await ) |
Testing multiple items | getAllBy... |
Elements that might appear later | findAllBy... (with await ) |
Non-accessible elements (last resort) | getByTestId |
Accessibility First: Use getByRole
whenever possible
Debug Helper: Add screen.debug()
to see current DOM
Async Patterns: Always await
for:
userEvent
interactionsfindBy...
querieswaitFor
assertionsMock Effectively:
// Vitest API mocks
vi.mock('../api.js', () => ({
fetchData: vi.fn(() => Promise.resolve({ data: [] }))
}));
Test User Flows, not implementation details