Skip to main content Skills Marketplace 커뮤니티가 만든 AI 스킬을 발견하고 탐색하세요.
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