🔍 Essential Queries (RTL)

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

🧪 Basic Test Structure

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();
  });
});

🖱️ Key UserEvent Actions

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)

✅ Essential Assertions

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', '#')

🚦 When to Use Which Query

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

💡 Beginner Tips

  1. Accessibility First: Use getByRole whenever possible

  2. Debug Helper: Add screen.debug() to see current DOM

  3. Async Patterns: Always await for:

  4. Mock Effectively:

    // Vitest API mocks
    vi.mock('../api.js', () => ({
      fetchData: vi.fn(() => Promise.resolve({ data: [] }))
    }));
    
  5. Test User Flows, not implementation details

📝 Practical Example: Form Test