Master Testing Library query methods (getBy, queryBy, findBy) to find elements using accessibility-first selectors and best practices.
Master Testing Library's query methods to find elements on the page using accessibility-first selectors. Learn the differences between `getBy`, `queryBy`, and `findBy` queries, understand query priority based on accessibility best practices, and write tests that resemble how users interact with your application.
Testing Library provides three query variants with different behaviors:
**Single Element Queries:**
**Multiple Element Queries:**
**Decision Matrix:**
| Query Type | 0 Matches | 1 Match | >1 Matches | Async |
|------------|-----------|---------|------------|-------|
| `getBy` | Throw | Return | Throw | No |
| `queryBy` | null | Return | Throw | No |
| `findBy` | Throw | Return | Throw | Yes |
Use queries in this priority order based on accessibility:
**1. Accessible to Everyone (Prefer These):**
```typescript
// Role queries (most accessible, top priority)
screen.getByRole('button', { name: /submit/i })
screen.getByRole('textbox', { name: /username/i })
screen.getByRole('heading', { name: /welcome/i })
// Label queries (best for form fields)
screen.getByLabelText('Email Address')
screen.getByLabelText(/password/i)
// Placeholder queries (fallback for forms)
screen.getByPlaceholderText('Enter your email')
// Text queries (for non-interactive content)
screen.getByText('Welcome to our app')
screen.getByText(/sign in/i)
// Display value queries (for filled forms)
screen.getByDisplayValue('[email protected]')
```
**2. Semantic Queries (Use Sparingly):**
```typescript
// Alt text (for images and accessible elements)
screen.getByAltText('Company logo')
// Title attribute (not always read by screen readers)
screen.getByTitle('Close dialog')
```
**3. Test IDs (Last Resort):**
```typescript
// Only when role/text queries won't work
screen.getByTestId('custom-element-123')
```
Import `screen` to query `document.body` without providing a container:
```typescript
import { render, screen } from '@testing-library/react'
test('should show login form', () => {
render(<Login />)
// Use screen for all queries (recommended)
const input = screen.getByLabelText('Username')
const button = screen.getByRole('button', { name: /submit/i })
// Without screen, you'd need to provide container
// const { getByLabelText } = render(<Login />)
// const input = getByLabelText('Username')
})
```
Queries accept strings, regex, or functions:
```typescript
// String matching (exact by default)
screen.getByText('Hello World')
screen.getByText('llo Worl', { exact: false }) // substring
// Regex matching (more flexible)
screen.getByText(/world/i) // case-insensitive
screen.getByText(/^hello world$/i) // full match
screen.getByText(/Hello W?oRlD/i) // pattern match
// Function matching (custom logic)
screen.getByText((content, element) => {
return element?.tagName.toLowerCase() === 'span' &&
content.startsWith('Hello')
})
```
Use `findBy` queries for elements that appear after async operations:
```typescript
import { render, screen } from '@testing-library/react'
test('should load user data', async () => {
render(<UserProfile userId="123" />)
// findBy returns a Promise (default timeout: 1000ms)
const userName = await screen.findByText('John Doe')
expect(userName).toBeInTheDocument()
// With custom timeout
const avatar = await screen.findByAltText(
'User avatar',
{},
{ timeout: 3000 }
)
})
```
Use `All` variants when multiple matches are expected:
```typescript
test('should render list items', () => {
render(<TodoList />)
// getAllBy throws if no matches
const items = screen.getAllByRole('listitem')
expect(items).toHaveLength(5)
// queryAllBy returns [] if no matches
const emptyItems = screen.queryAllByRole('listitem')
expect(emptyItems).toEqual([])
// findAllBy for async lists
const asyncItems = await screen.findAllByRole('listitem')
expect(asyncItems).toHaveLength(5)
})
```
Use `queryBy` (not `getBy`) to assert elements don't exist:
```typescript
test('should not show error initially', () => {
render(<LoginForm />)
// queryBy returns null if not found (no error thrown)
const error = screen.queryByRole('alert')
expect(error).not.toBeInTheDocument()
// getBy would throw an error here (wrong approach)
// expect(() => screen.getByRole('alert')).toThrow()
})
```
Control matching behavior with precision options:
```typescript
// Case-insensitive substring match
screen.getByText('hello', { exact: false })
// Custom normalization (remove Unicode, extra whitespace)
screen.getByText('Hello World', {
normalizer: (str) => str.replace(/\s+/g, ' ').trim()
})
// Disable default normalization
import { getDefaultNormalizer } from '@testing-library/react'
screen.getByText('Exact Spaces', {
normalizer: getDefaultNormalizer({ trim: false, collapseWhitespace: false })
})
```
Use `within` to scope queries to a specific container:
```typescript
import { render, screen, within } from '@testing-library/react'
test('should find button within dialog', () => {
render(<App />)
const dialog = screen.getByRole('dialog')
const closeButton = within(dialog).getByRole('button', { name: /close/i })
})
```
Use role options to narrow down elements:
```typescript
// Find button by accessible name
screen.getByRole('button', { name: /submit/i })
// Find link by accessible name
screen.getByRole('link', { name: /learn more/i })
// Find checkbox by label
screen.getByRole('checkbox', { name: /agree to terms/i })
// Find heading by level and name
screen.getByRole('heading', { level: 2, name: /welcome/i })
```
1. **Prefer `getByRole` with `name` option** — Most accessible, works with assistive tech
2. **Use `getByLabelText` for forms** — Mimics how users find form fields
3. **Use `queryBy` for absence assertions** — `getBy` throws, `queryBy` returns null
4. **Use `findBy` for async** — Automatically retries with waitFor behavior
5. **Avoid `getByTestId` when possible** — Use semantic queries that reflect user experience
6. **Use regex for flexible matching** — Better than `{ exact: false }` in most cases
7. **Query from `screen` by default** — Cleaner syntax, no container needed
```typescript
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { LoginForm } from './LoginForm'
test('should submit login form', async () => {
const user = userEvent.setup()
const handleSubmit = jest.fn()
render(<LoginForm onSubmit={handleSubmit} />)
// Find form fields (priority: role > label)
const emailInput = screen.getByRole('textbox', { name: /email/i })
const passwordInput = screen.getByLabelText(/password/i)
const submitButton = screen.getByRole('button', { name: /sign in/i })
// Assert no error initially
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
// Simulate user interaction
await user.type(emailInput, '[email protected]')
await user.type(passwordInput, 'password123')
await user.click(submitButton)
// Wait for async success message
const successMessage = await screen.findByText(/welcome back/i)
expect(successMessage).toBeInTheDocument()
// Assert callback was called
expect(handleSubmit).toHaveBeenCalledWith({
email: '[email protected]',
password: 'password123'
})
})
```
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/testing-library-queries/raw