testing-library

安装量: 206
排名: #4216

安装

npx skills add https://github.com/jezweb/claude-skills --skill testing-library
React Testing Library
Status
Production Ready
Last Updated
2026-02-06
Version
16.x
User Event
14.x Quick Start

Install with Vitest

pnpm add -D @testing-library/react @testing-library/user-event @testing-library/jest-dom jsdom

Or with Jest

pnpm add -D @testing-library/react @testing-library/user-event @testing-library/jest-dom Setup File (src/test/setup.ts) import '@testing-library/jest-dom/vitest' ; import { cleanup } from '@testing-library/react' ; import { afterEach } from 'vitest' ; // Cleanup after each test afterEach ( ( ) => { cleanup ( ) ; } ) ; Vitest Config // vitest.config.ts export default defineConfig ( { test : { globals : true , environment : 'jsdom' , setupFiles : [ './src/test/setup.ts' ] , } , } ) ; Query Priority (Accessibility First) Use queries in this order for accessible, resilient tests: Priority Query Use For 1 getByRole Buttons, links, headings, inputs 2 getByLabelText Form inputs with labels 3 getByPlaceholderText Inputs without visible labels 4 getByText Non-interactive text content 5 getByTestId Last resort only Examples import { render , screen } from '@testing-library/react' ; // ✅ GOOD - semantic role queries screen . getByRole ( 'button' , { name : / submit / i } ) ; screen . getByRole ( 'heading' , { level : 1 } ) ; screen . getByRole ( 'textbox' , { name : / email / i } ) ; screen . getByRole ( 'link' , { name : / learn more / i } ) ; // ✅ GOOD - label-based queries for forms screen . getByLabelText ( / email address / i ) ; // ⚠️ OK - when no better option screen . getByText ( / welcome to our app / i ) ; // ❌ AVOID - not accessible, brittle screen . getByTestId ( 'submit-button' ) ; Query Variants Variant Returns Throws Use For getBy Element Yes Element exists now queryBy Element or null No Element might not exist findBy Promise Yes Async, appears later getAllBy Element[] Yes Multiple elements queryAllBy Element[] No Multiple or none findAllBy Promise Yes Multiple, async When to Use Each // Element exists immediately const button = screen . getByRole ( 'button' ) ; // Check element doesn't exist expect ( screen . queryByRole ( 'dialog' ) ) . not . toBeInTheDocument ( ) ; // Wait for async element to appear const modal = await screen . findByRole ( 'dialog' ) ; // Multiple elements const items = screen . getAllByRole ( 'listitem' ) ; User Event (Realistic Interactions) Always use userEvent over fireEvent - it simulates real user behavior. import { render , screen } from '@testing-library/react' ; import userEvent from '@testing-library/user-event' ; describe ( 'Form' , ( ) => { it ( 'submits form data' , async ( ) => { const user = userEvent . setup ( ) ; const onSubmit = vi . fn ( ) ; render ( < LoginForm onSubmit = { onSubmit } /

) ; // Type in inputs await user . type ( screen . getByLabelText ( / email / i ) , 'test@example.com' ) ; await user . type ( screen . getByLabelText ( / password / i ) , 'secret123' ) ; // Click submit await user . click ( screen . getByRole ( 'button' , { name : / sign in / i } ) ) ; expect ( onSubmit ) . toHaveBeenCalledWith ( { email : 'test@example.com' , password : 'secret123' , } ) ; } ) ; } ) ; Common User Events const user = userEvent . setup ( ) ; // Clicking await user . click ( element ) ; await user . dblClick ( element ) ; await user . tripleClick ( element ) ; // Select all text // Typing await user . type ( input , 'hello world' ) ; await user . clear ( input ) ; await user . type ( input , '{Enter}' ) ; // Special keys // Keyboard await user . keyboard ( '{Shift>}A{/Shift}' ) ; // Shift+A await user . tab ( ) ; // Tab navigation // Selection await user . selectOptions ( select , [ 'option1' , 'option2' ] ) ; // Hover await user . hover ( element ) ; await user . unhover ( element ) ; // Clipboard await user . copy ( ) ; await user . paste ( ) ; Async Testing findBy - Wait for Element it ( 'shows loading then content' , async ( ) => { render ( < AsyncComponent /

) ; // Shows loading initially expect ( screen . getByText ( / loading / i ) ) . toBeInTheDocument ( ) ; // Wait for content to appear (auto-retries) const content = await screen . findByText ( / data loaded / i ) ; expect ( content ) . toBeInTheDocument ( ) ; } ) ; waitFor - Wait for Condition import { waitFor } from '@testing-library/react' ; it ( 'updates count after click' , async ( ) => { const user = userEvent . setup ( ) ; render ( < Counter /

) ; await user . click ( screen . getByRole ( 'button' , { name : / increment / i } ) ) ; // Wait for state update await waitFor ( ( ) => { expect ( screen . getByText ( / count: 1 / i ) ) . toBeInTheDocument ( ) ; } ) ; } ) ; waitForElementToBeRemoved import { waitForElementToBeRemoved } from '@testing-library/react' ; it ( 'hides modal after close' , async ( ) => { const user = userEvent . setup ( ) ; render ( < ModalComponent /

) ; await user . click ( screen . getByRole ( 'button' , { name : / close / i } ) ) ; // Wait for modal to disappear await waitForElementToBeRemoved ( ( ) => screen . queryByRole ( 'dialog' ) ) ; } ) ; MSW Integration (API Mocking) Mock API calls at the network level with Mock Service Worker. pnpm add -D msw Setup (src/test/mocks/handlers.ts) import { http , HttpResponse } from 'msw' ; export const handlers = [ http . get ( '/api/user' , ( ) => { return HttpResponse . json ( { id : 1 , name : 'Test User' , email : 'test@example.com' , } ) ; } ) , http . post ( '/api/login' , async ( { request } ) => { const body = await request . json ( ) ; if ( body . password === 'correct' ) { return HttpResponse . json ( { token : 'abc123' } ) ; } return HttpResponse . json ( { error : 'Invalid credentials' } , { status : 401 } ) ; } ) , ] ; Setup (src/test/mocks/server.ts) import { setupServer } from 'msw/node' ; import { handlers } from './handlers' ; export const server = setupServer ( ... handlers ) ; Test Setup // src/test/setup.ts import { server } from './mocks/server' ; import { beforeAll , afterEach , afterAll } from 'vitest' ; beforeAll ( ( ) => server . listen ( { onUnhandledRequest : 'error' } ) ) ; afterEach ( ( ) => server . resetHandlers ( ) ) ; afterAll ( ( ) => server . close ( ) ) ; Using in Tests import { server } from '../test/mocks/server' ; import { http , HttpResponse } from 'msw' ; it ( 'handles API error' , async ( ) => { // Override handler for this test server . use ( http . get ( '/api/user' , ( ) => { return HttpResponse . json ( { error : 'Server error' } , { status : 500 } ) ; } ) ) ; render ( < UserProfile /

) ; await screen . findByText ( / error loading user / i ) ; } ) ; Accessibility Testing Check for A11y Violations pnpm add -D @axe-core/react import { axe , toHaveNoViolations } from 'jest-axe' ; expect . extend ( toHaveNoViolations ) ; it ( 'has no accessibility violations' , async ( ) => { const { container } = render ( < MyComponent /

) ; const results = await axe ( container ) ; expect ( results ) . toHaveNoViolations ( ) ; } ) ; Role-Based Queries Are A11y Tests Using getByRole implicitly tests accessibility: // This passes only if button is properly accessible screen . getByRole ( 'button' , { name : / submit / i } ) ; // Fails if: // - Element isn't a button or role="button" // - Accessible name doesn't match // - Element is hidden from accessibility tree Testing Patterns Forms it ( 'validates required fields' , async ( ) => { const user = userEvent . setup ( ) ; render ( < ContactForm /

) ; // Submit without filling required fields await user . click ( screen . getByRole ( 'button' , { name : / submit / i } ) ) ; // Check for validation errors expect ( screen . getByText ( / email is required / i ) ) . toBeInTheDocument ( ) ; expect ( screen . getByText ( / message is required / i ) ) . toBeInTheDocument ( ) ; } ) ; Modals/Dialogs it ( 'opens and closes modal' , async ( ) => { const user = userEvent . setup ( ) ; render ( < ModalTrigger /

) ; // Modal not visible initially expect ( screen . queryByRole ( 'dialog' ) ) . not . toBeInTheDocument ( ) ; // Open modal await user . click ( screen . getByRole ( 'button' , { name : / open / i } ) ) ; expect ( screen . getByRole ( 'dialog' ) ) . toBeInTheDocument ( ) ; // Close modal await user . click ( screen . getByRole ( 'button' , { name : / close / i } ) ) ; await waitForElementToBeRemoved ( ( ) => screen . queryByRole ( 'dialog' ) ) ; } ) ; Lists it ( 'renders list items' , ( ) => { render ( < TodoList items = { [ 'Buy milk' , 'Walk dog' ] } /

) ; const items = screen . getAllByRole ( 'listitem' ) ; expect ( items ) . toHaveLength ( 2 ) ; expect ( items [ 0 ] ) . toHaveTextContent ( 'Buy milk' ) ; } ) ; Common Matchers (jest-dom) // Presence expect ( element ) . toBeInTheDocument ( ) ; expect ( element ) . toBeVisible ( ) ; expect ( element ) . toBeEmptyDOMElement ( ) ; // State expect ( button ) . toBeEnabled ( ) ; expect ( button ) . toBeDisabled ( ) ; expect ( checkbox ) . toBeChecked ( ) ; expect ( input ) . toBeRequired ( ) ; // Content expect ( element ) . toHaveTextContent ( / hello / i ) ; expect ( element ) . toHaveValue ( 'test' ) ; expect ( element ) . toHaveAttribute ( 'href' , '/about' ) ; // Styles expect ( element ) . toHaveClass ( 'active' ) ; expect ( element ) . toHaveStyle ( { color : 'red' } ) ; // Focus expect ( input ) . toHaveFocus ( ) ; Debugging screen.debug() it ( 'debugs rendering' , ( ) => { render ( < MyComponent /

) ; // Print entire DOM screen . debug ( ) ; // Print specific element screen . debug ( screen . getByRole ( 'button' ) ) ; } ) ; logRoles import { logRoles } from '@testing-library/react' ; it ( 'shows available roles' , ( ) => { const { container } = render ( < MyComponent /

) ; logRoles ( container ) ; } ) ; Common Mistakes Using getBy for Async // ❌ WRONG - fails if element appears async const modal = screen . getByRole ( 'dialog' ) ; // ✅ CORRECT - waits for element const modal = await screen . findByRole ( 'dialog' ) ; Not Awaiting User Events // ❌ WRONG - race condition user . click ( button ) ; expect ( result ) . toBeInTheDocument ( ) ; // ✅ CORRECT - await the interaction await user . click ( button ) ; expect ( result ) . toBeInTheDocument ( ) ; Using container.querySelector // ❌ WRONG - not accessible, brittle const button = container . querySelector ( '.submit-btn' ) ; // ✅ CORRECT - accessible query const button = screen . getByRole ( 'button' , { name : / submit / i } ) ; See Also vitest skill - Test runner configuration testing-patterns skill - General testing patterns Official docs: https://testing-library.com/docs/react-testing-library/intro

返回排行榜