Skip to main content Skills Marketplace 发现并探索由社区构建的 Agent Skills
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/pluginagentmarketplace/custom-plugin-react --skill react-testing-library命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
下载 Zip 下载中... 同仓库更多 Skills Build production-grade, accessible, and tested component libraries with Storybook, Chromatic, and design tokens
Build production-grade React apps with Next.js 14 App Router, Server Components, and Edge Runtime
Master React Hooks including useState, useEffect, useContext, useReducer, and custom hooks with production-grade patterns
pluginagentmarketplace
pluginagentmarketplace/custom-plugin-react
打开 GitHub 仓库 name react-testing-library description Write production-grade React tests with Testing Library, MSW, and comprehensive coverage patterns sasmp_version 2.0.0 bonded_agent 07-testing-deployment bond_type PRIMARY_BOND input_validation {"required_packages":[{"@testing-library/react":">=14.0.0"},{"@testing-library/user-event":">=14.0.0"},{"msw":">=2.0.0"}]} output_format {"code_examples":"jsx","coverage_target":80} error_handling {"patterns":["retry_flaky","async_waitfor","mock_error_states"]} observability {"logging":"jest_console","metrics":["test_duration","coverage"]}
React Testing Library Skill
Overview
Master React Testing Library for writing maintainable tests that focus on user behavior rather than implementation details.
Learning Objectives
Write component tests with RTL
Test user interactions
Handle async operations
Test hooks and context
Follow testing best practices
Quick Start
Installation
npm install --save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-event
Setup
import '@testing-library/jest-dom' ;
Basic Component Testing
import { render, screen } from '@testing-library/react' ;
import userEvent from '@testing-library/user-event' ;
import Button from './Button' ;
describe ('Button' , () => {
it ('renders button text' , () => {
render (<Button > Click me</Button > );
expect (screen.getByRole ('button' , { name : /click me/i })).toBeInTheDocument ();
});
it ('calls onClick when clicked' , async () => {
const handleClick = jest.fn ();
( );
userEvent. (screen. ( ));
(handleClick). ( );
});
( , {
( );
(screen. ( )). ();
});
});
render
<Button onClick ={handleClick} > Click me</Button >
await
click
getByRole
'button'
expect
toHaveBeenCalledTimes
1
it
'is disabled when disabled prop is true'
() =>
render
<Button disabled > Click me</Button >
expect
getByRole
'button'
toBeDisabled
Queries
Priority Order
screen.getByRole ('button' , { name : /submit/i });
screen.getByLabelText ('Email' );
screen.getByPlaceholderText ('Enter email' );
screen.getByText ('Welcome' );
screen.getByAltText ('Profile picture' );
screen.getByTitle ('Close' );
screen.getByTestId ('custom-element' );
Query Variants
screen.getByText ('Hello' );
screen.queryByText ('Hello' );
await screen.findByText ('Hello' );
Testing Forms
import { render, screen } from '@testing-library/react' ;
import userEvent from '@testing-library/user-event' ;
import LoginForm from './LoginForm' ;
describe ('LoginForm' , () => {
it ('submits form with correct data' , async () => {
const handleSubmit = jest.fn ();
render (<LoginForm onSubmit ={handleSubmit} /> );
await userEvent.type (screen.getByLabelText (/email/i ), 'test@example.com' );
await userEvent.type (screen.getByLabelText (/password/i ), 'password123' );
await userEvent.click (screen.getByRole ('button' , { name : /login/i }));
expect (handleSubmit).toHaveBeenCalledWith ({
email : 'test@example.com' ,
password : 'password123'
});
});
it ('shows validation errors' , async () => {
render (<LoginForm onSubmit ={jest.fn()} /> );
await userEvent.click (screen.getByRole ('button' , { name : /login/i }));
expect (await screen.findByText (/email is required/i )).toBeInTheDocument ();
});
});
Testing Async Operations
import { render, screen, waitFor } from '@testing-library/react' ;
import { rest } from 'msw' ;
import { setupServer } from 'msw/node' ;
import UserProfile from './UserProfile' ;
const server = setupServer (
rest.get ('/api/users/:userId' , (req, res, ctx ) => {
return res (ctx.json ({ id : 1 , name : 'John Doe' }));
})
);
beforeAll (() => server.listen ());
afterEach (() => server.resetHandlers ());
afterAll (() => server.close ());
describe ('UserProfile' , () => {
it ('loads and displays user data' , async () => {
render (<UserProfile userId ={1} /> );
expect (screen.getByText (/loading/i )).toBeInTheDocument ();
expect (await screen.findByText ('John Doe' )).toBeInTheDocument ();
expect (screen.queryByText (/loading/i )).not .toBeInTheDocument ();
});
it ('handles API errors' , async () => {
server.use (
rest.get ('/api/users/:userId' , (req, res, ctx ) => {
return res (ctx.status (500 ));
})
);
render (<UserProfile userId ={1} /> );
expect (await screen.findByText (/error/i )).toBeInTheDocument ();
});
});
Testing Hooks
import { renderHook, act } from '@testing-library/react' ;
import useCounter from './useCounter' ;
describe ('useCounter' , () => {
it ('increments counter' , () => {
const { result } = renderHook (() => useCounter ());
act (() => {
result.current .increment ();
});
expect (result.current .count ).toBe (1 );
});
it ('resets to initial value' , () => {
const { result } = renderHook (() => useCounter (10 ));
act (() => {
result.current .increment ();
result.current .reset ();
});
expect (result.current .count ).toBe (10 );
});
});
Testing Context
import { render, screen } from '@testing-library/react' ;
import userEvent from '@testing-library/user-event' ;
import { ThemeProvider } from './ThemeContext' ;
import ThemeToggle from './ThemeToggle' ;
describe ('ThemeToggle' , () => {
it ('toggles theme' , async () => {
render (
<ThemeProvider >
<ThemeToggle />
</ThemeProvider >
);
expect (screen.getByText (/current theme: light/i )).toBeInTheDocument ();
await userEvent.click (screen.getByRole ('button' ));
expect (screen.getByText (/current theme: dark/i )).toBeInTheDocument ();
});
});
Custom Render Utility
import { render } from '@testing-library/react' ;
import { BrowserRouter } from 'react-router-dom' ;
import { ThemeProvider } from './ThemeContext' ;
function AllProviders ({ children } ) {
return (
<BrowserRouter >
<ThemeProvider >
{children}
</ThemeProvider >
</BrowserRouter >
);
}
function customRender (ui, options ) {
return render (ui, { wrapper : AllProviders , ...options });
}
export * from '@testing-library/react' ;
export { customRender as render };
Best Practices
Test user behavior, not implementation
Use accessible queries (role, label)
Avoid testing internal state
Use userEvent for realistic interactions
Mock external dependencies (API calls)
Test error states and edge cases
Keep tests simple and focused
Common Patterns
await waitFor (() => {
expect (screen.queryByText (/loading/i )).not .toBeInTheDocument ();
});
await waitFor (() => {
expect (screen.getAllByRole ('listitem' )).toHaveLength (3 );
});
screen.debug ();
screen.logTestingPlaygroundURL ();
Practice Exercises
Test form validation
Test async data fetching
Test user authentication flow
Test routing navigation
Test modal interactions
Test list filtering
Test error boundaries
Resources
Flaky Test Prevention
module .exports = {
testRetry : process.env .CI ? 2 : 0 ,
testTimeout : 10000 ,
};
it ('handles async operations reliably' , async () => {
render (<AsyncComponent /> );
expect (await screen.findByText (/loaded/i , {}, { timeout : 5000 }))
.toBeInTheDocument ();
await waitFor (() => {
expect (screen.queryByText (/loading/i )).not .toBeInTheDocument ();
});
});
MSW 2.0 Setup
import { http, HttpResponse } from 'msw' ;
export const handlers = [
http.get ('/api/users' , () => {
return HttpResponse .json ([{ id : 1 , name : 'John' }]);
}),
http.post ('/api/users' , async ({ request }) => {
const user = await request.json ();
return HttpResponse .json ({ ...user, id : Date .now () }, { status : 201 });
}),
http.get ('/api/error' , () => {
return HttpResponse .json ({ message : 'Server Error' }, { status : 500 });
}),
];
Coverage Configuration
module .exports = {
collectCoverageFrom : ['src/**/*.{js,jsx,ts,tsx}' , '!src/**/*.d.ts' ],
coverageThreshold : {
global : {
branches : 80 ,
functions : 80 ,
lines : 80 ,
statements : 80 ,
},
},
};
Version : 2.0.0
Last Updated : 2025-12-30
SASMP Version : 2.0.0
Difficulty : Intermediate
Estimated Time : 2-3 weeks
Prerequisites : React Fundamentals, Jest Basics
Changelog : Added MSW 2.0, flaky test prevention, and coverage config