Recipes
Testing LibraryPatterns
Practical recipes for writing resilient component tests with Testing Library. Queries, async patterns, and accessibility-first assertions.
Query Priority
// 1. Accessible queries (preferred)
screen.getByRole('button', { name: /submit/i })
screen.getByLabelText('Email address')
// 2. Semantic queries
screen.getByText(/welcome back/i)
screen.getByDisplayValue('user@nimbus.dev')
// 3. Test IDs (last resort)
screen.getByTestId('license-key-input')Async State
// findBy* — retries until element appears
const toast = await screen.findByRole('alert')
expect(toast).toHaveTextContent('License activated')
// waitFor — arbitrary async assertion
await waitFor(() => {
expect(mockValidateKey).toHaveBeenCalledTimes(1)
})
// waitForElementToBeRemoved — disappearance
await waitForElementToBeRemoved(() =>
screen.queryByRole('progressbar')
)User Events
import userEvent from '@testing-library/user-event'
const user = userEvent.setup()
await user.type(screen.getByLabelText('Key'), 'NIMBUS-XXXX')
await user.click(screen.getByRole('button', { name: /activate/i }))
// Keyboard shortcuts
await user.keyboard('{Enter}')
await user.tab()Custom Render
// Wrap with providers once
function renderWithProviders(ui: ReactElement) {
return render(
<QueryClientProvider client={queryClient}>
{ui}
</QueryClientProvider>
)
}
// Use in every test
renderWithProviders(<Dashboard />)