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 />)