Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/CodySwannGT/lisa --skill testing-library명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | testing-library |
| description | Enforces best practices for… |
This skill enforces best practices for unit testing in Expo applications using Jest, @testing-library/react-native, and jest-expo. Tests should be user-centric, accessible, and behavior-focused rather than implementation-focused.
Focus on what the component does from a user's perspective, not how it achieves it internally.
// Correct - tests visible behavior
expect(screen.getByRole("button", { name: "Submit" })).toBeEnabled();
// Incorrect - tests implementation details
expect(component.state.isSubmitting).toBe(false);
Queries should reflect how users and assistive technologies interact with the UI.
// Correct - uses accessible role and name
screen.getByRole("button", { name: /save changes/i });
// Incorrect - relies on implementation detail
screen.getByTestId("save-btn");
Each test should verify one behavior. Multiple assertions are acceptable when verifying different aspects of the same behavior.
// Correct - focused test
test("displays error message when submission fails", async () => {
render(<Form />);
await userEvent.press(screen.getByRole("button", { name: "Submit" }));
expect(await screen.findByRole("alert")).toHaveTextContent("Failed");
});
// Incorrect - testing multiple behaviors
test("form works correctly", async () => {
// Tests validation, submission, success, and error handling...
});
userEvent simulates realistic user interactions including the full event sequence.
// Correct - realistic interaction
const user = userEvent.setup();
await user.press(screen.getByRole("button", { name: "Submit" }));
// Less ideal - simplified event
fireEvent.press(screen.getByRole("button", { name: "Submit" }));
Choose queries based on accessibility, following this priority order:
| Priority | Query | When to Use |
|---|---|---|
| 1 | getByRole | Interactive elements, headings, buttons |
| 2 | getByLabelText | Form fields with labels |
| 3 | getByText | Non-interactive content, static text |
| 4 | getByTestId | Last resort when semantic queries fail |
For detailed query patterns, see references/query-priority.md.
// Correct - waits for element to appear
expect(await screen.findByRole("alert")).toBeOnTheScreen();
// Incorrect - may fail if element appears async
expect(screen.getByRole("alert")).toBeOnTheScreen();
// Correct - assertion inside waitFor
await waitFor(() => {
expect(mockCallback).toHaveBeenCalledWith("success");
});
// Incorrect - side effect inside waitFor
await waitFor(() => {
fireEvent.press(button); // Never do this
});
For comprehensive async patterns, see references/async-patterns.md.
These must be configured in jest/setup-jest.ts:
// AsyncStorage
jest.mock("@react-native-async-storage/async-storage", () =>
require("@react-native-async-storage/async-storage/jest/async-storage-mock")
);
// Expo Fonts (to avoid async icon assertions)
jest.mock("expo-font", () => ({
...jest.requireActual("expo-font"),
isLoaded: jest.fn(() => true),
}));
For complete mocking patterns, see references/mocking-patterns.md.
Use renderRouter from expo-router/testing-library instead of render when testing components that use Expo Router.
import { renderRouter, screen } from "expo-router/testing-library";
test("navigates to player detail", async () => {
renderRouter({
index: () => <PlayerList />,
"players/[id]": () => <PlayerDetail />,
});
await userEvent.press(screen.getByRole("button", { name: "View Player" }));
expect(screen).toHavePathname("/players/123");
});
For Expo Router testing details, see references/expo-router-testing.md.
__tests__/ directories, not alongside source filesapp/ directory (Expo Router constraint).test.ts or .test.tsx extensionsStructure every test with Arrange-Act-Assert:
test("increments counter when button pressed", async () => {
// Arrange
const user = userEvent.setup();
render(<Counter initialCount={0} />);
// Act
await user.press(screen.getByRole("button", { name: "Increment" }));
// Assert
expect(screen.getByRole("text", { name: "Count: 1" })).toBeOnTheScreen();
});
Use descriptive names that explain the expected behavior:
// Correct - describes behavior
test("displays validation error when email format is invalid", () => {});
test("disables submit button while form is submitting", () => {});
// Incorrect - vague or implementation-focused
test("email validation works", () => {});
test("sets isSubmitting to true", () => {});
Lisa configures Jest manually instead of using the jest-expo preset to avoid
jsdom incompatibility with react-native/jest/setup.js. The configuration in
jest.expo.ts provides haste, resolver, transform, and setupFiles that match
the preset's behavior without redefining window.
jest.useFakeTimers();
test("handles debounced input", async () => {
const user = userEvent.setup();
render(<SearchInput />);
await user.type(screen.getByRole("textbox"), "query");
jest.runAllTimers();
expect(await screen.findByText("Results")).toBeOnTheScreen();
});
// Wrong - testing internal state
expect(wrapper.state().isLoading).toBe(true);
// Wrong - testing component methods
expect(wrapper.instance().handleSubmit).toHaveBeenCalled();
// Correct - testing visible behavior
expect(screen.getByRole("progressbar")).toBeOnTheScreen();
// Wrong - using testID when semantic query exists
screen.getByTestId("submit-button");
// Correct - using accessible query
screen.getByRole("button", { name: "Submit" });
// Wrong - unnecessary act wrapper
await act(async () => {
render(<Component />);
});
// Correct - render already wraps in act
render(<Component />);
// Wrong - side effect in waitFor
await waitFor(() => {
fireEvent.press(button);
expect(result).toBeOnTheScreen();
});
// Correct - side effect before waitFor
fireEvent.press(button);
await waitFor(() => {
expect(result).toBeOnTheScreen();
});
// Wrong - multiple assertions
await waitFor(() => {
expect(title).toBeOnTheScreen();
expect(subtitle).toBeOnTheScreen();
expect(button).toBeEnabled();
});
// Correct - single assertion, chain with findBy
expect(await screen.findByRole("heading")).toBeOnTheScreen();
expect(screen.getByText("Subtitle")).toBeOnTheScreen();
expect(screen.getByRole("button")).toBeEnabled();
| Matcher | Purpose |
|---|---|
toBeOnTheScreen() | Element is currently rendered |
toBeEnabled() | Interactive element is enabled |
toBeDisabled() | Interactive element is disabled |
toHaveTextContent() | Element contains text |
toBeVisible() | Element is visible to user |
toBeChecked() | Checkbox/radio is checked |
| Prefix | Returns | Throws on 0 | Throws on >1 | Async |
|---|---|---|---|---|
| getBy | Element | Yes | Yes | No |
| queryBy | Element | null | No | Yes | No |
| findBy | Promise<Element> | Yes | Yes | Yes |
| getAllBy | Element[] | Yes | No | No |
| queryAllBy | Element[] | No | No | No |
| findAllBy | Promise<Element[]> | Yes | No | Yes |