Skip to main content

unit-tests

Jest + React Testing Library best practices for Wonder Blocks unit tests. Use when creating or editing `.test.ts` / `.test.tsx` files.

소스 정보

저장소
Khan/wonder-blocks
최근 소스 활동
2026년 5월 7일 17:42
감지된 SKILL.md 언어
영어
스타
163
포크
12

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
unit-tests
description
Jest + React Testing Library best practices for Wonder Blocks unit tests. Use when creating or editing `.test.ts` / `.test.tsx` files.
# Jest Testing Best Practices This guide covers testing patterns and best practices for Jest and React Testing Library in the Wonder Blocks codebase. ## Core Testing Principles ### ⚠️ Critical Setup Rules **Test Workflow Priority:** - ✅ **ALWAYS fix failing tests BEFORE fixing linting errors** - ✅ **Focus on underlying errors, not `Unhandled console.error call` messages** - ⚠️ When tests fail with `Unhandled console.error call`, look for the **root cause error** (e.g., `ReferenceError: window is not defined`) - ⚠️ The console.error messages are symptoms, not the actual problem - fix the underlying issue **File Structure:** - ✅ Name test files with `.test.ts` or `.test.tsx` suffix - ✅ Place in `__tests__/` directory OR colocate with source files (follow local conventions) **Test Framework Setup:** - ✅ Additional matchers from React Testing Library (RTL) and `jest-extended` are available - ✅ Use `describe`/`it` pattern for test organization - ✅ Use `globalThis` prefix when accessing global objects - ✅ **Prioritize testing non-trivial business logic** over trivial implementations ### Arrange-Act-Assert Pattern **⚠️ ALWAYS use this three-section structure:** ```typescript describe("Calculator", () => { it("should add two numbers correctly", () => { // Arrange const a = 5; const b = 3; // Act const result = add(a, b); // Assert expect(result).toBe(8); }); }); ``` **Rules:** - ✅ **ALWAYS divide tests** into Arrange, Act, Assert sections with comments - ✅ **Each section gets exactly one comment label** (`// Arrange`, `// Act`, `// Assert`) — no additional comments within a section - ❌ **NEVER combine sections** (e.g., don't write `// Act & Assert`) - ❌ **NEVER use multiple Act or Assert sections** in a single test (split into separate tests instead) - ❌ **NEVER remove Arrange, Act, Assert comments** **Exception - Testing Thrown Errors:** When testing errors, use an `underTest` variable in the Act section: ```typescript it("should throw an error when input is invalid", () => { // Arrange const invalidInput = "invalid"; // Act const underTest = () => { processInput(invalidInput); }; // Assert expect(underTest).toThrow("Invalid input"); }); ``` ### Be Concise and Avoid Over-Testing **⚠️ Focus on what matters - don't overdo it:** **DO Test:** - ✅ **Non-trivial business logic** - Complex calculations, data transformations, validation rules - ✅ **User interactions** - Click handlers, form submissions, keyboard navigation - ✅ **Accessibility** - ARIA attributes, keyboard support, focus management - ✅ **Edge cases and error conditions** - Null values, empty states, error handling - ✅ **Integration points** - API calls, event callbacks, state changes - ✅ **Bug fixes** - Add a test that reproduces the bug to prevent regressions **DON'T Test:** - ❌ **Trivial implementations** - Simple getters/setters, pass-through functions - ❌ **Style-only props** - Visual appearance is covered by visual regression tests in Storybook - ❌ **Third-party libraries** - Assume they work; test your usage of them - ❌ **Implementation details** - Internal state that doesn't affect output/behavior - ❌ **Additional logic in tests** - Use existing utility functions instead of reimplementing logic in tests ```typescript // ❌ DON'T: Testing style-only props (use visual regression tests instead) it("should apply primary color when kind is primary", () => { render(<Button kind="primary" />); expect(screen.getByRole("button")).toHaveStyle({ backgroundColor: "blue" }); }); // ✅ DO: Test meaningful behavior it("should call onClick when clicked", async () => { // Arrange const handleClick = jest.fn(); render(<Button onClick={handleClick}>Click me</Button>); // Act await userEvent.click(screen.getByRole("button")); // Assert expect(handleClick).toHaveBeenCalledTimes(1); }); // ✅ DO: Test non-trivial logic it("should validate email format and return error message", () => { // Arrange const invalidEmail = "not-an-email"; // Act const result = validateEmail(invalidEmail); // Assert expect(result).toBe("Please enter a valid email address"); }); ``` **Key Principles:** - ✅ **Test behavior, not implementation** - Focus on what the component does, not how - ✅ **Prioritize critical paths** - Test the most important user flows first - ✅ **Keep tests simple and readable** - Each test should have a clear, single purpose - ✅ **Don't add logic to tests** - Tests should only test the component/function; use existing utility functions from the codebase instead of reimplementing logic in tests - ✅ **Use visual regression tests for styling** - Storybook snapshot tests handle visual appearance - ✅ **Balance coverage with maintainability** - More tests ≠ better tests ### Assertions **Best Practices:** - ✅ **Use specific matchers** when possible (e.g., `toBe`, `toEqual`, `toHaveBeenCalledWith`) - ✅ **Prefer explicit assertions** over implicit ones - ✅ Use semantic matchers from RTL: `toBeInTheDocument()`, `toBeVisible()`, `toHaveAttribute()` - ❌ **Avoid Jest snapshots** (`.toMatchSnapshot()`, `.toMatchInlineSnapshot()`) - use Chromatic + Storybook for visual regression tests, or use specific attribute assertions instead ### One Expect Per Test **⚠️ Each test should have exactly one `expect`.** If you need to assert multiple things, split them into separate tests. Multiple assertions hide which behavior actually broke when the test fails. ### Parameterized Tests with `it.each` **When to use:** Testing the same logic with multiple input/output combinations **✅ DO: Use `it.each` for data-driven tests** ```typescript describe("Calculator", () => { it.each([ [2, 3, 5], [0, 0, 0], [-1, 1, 0], [10, -5, 5], ])("should add %i and %i to equal %i", (a, b, expected) => { // Arrange // (inputs come from it.each) // Act const result = add(a, b); // Assert expect(result).toBe(expected); }); }); ``` **Benefits:** - ✅ **Reduces Duplication**: Test same logic with different inputs - ✅ **Clear Test Names**: Each test shows specific values being tested - ✅ **Easy to Extend**: Simply add new arrays to test data - ✅ **Better Coverage**: Test edge cases and boundary conditions efficiently - ✅ **Comprehensive Testing**: Essential for testing all prop combinations and states in Wonder Blocks components ## Mocking and Spying ### ⚠️ Critical Rules - ALWAYS Follow These 1. **NEVER mock `console.error`** - This hides real implementation issues and errors 2. **ALWAYS use `jest.spyOn()` to create spies** - Never treat the original function as though it were a spy 3. **Store spy return values in variables ONLY when asserting on them** - Avoids unused variable linter errors 4. **NEVER mock outside of tests** - Even if it means code duplication, keep mocks inside test cases ### Method Spying - Correct Pattern **✅ DO: Use jest.spyOn and store the result when asserting** ```typescript import * as SomeFile from "./some-file.ts"; describe("MyComponent", () => { it("should call someMethod with correct args", () => { // Arrange // Store spy because we'll assert on it later const spy = jest.spyOn(SomeFile, "someMethod").mockReturnValue(mockValue); // Act myFunction(); // Assert expect(spy).toHaveBeenCalledWith(expectedArgs); }); }); ``` **❌ DON'T: Treat the original function as a spy without jest.spyOn()** ```typescript // ❌ WRONG - This will fail because someMethod is not a spy import * as SomeFile from "./some-file.ts"; describe("MyComponent", () => { it("should call someMethod", () => { // Act myFunction(); // Assert expect(SomeFile.someMethod).toHaveBeenCalled(); // ❌ ERROR! Not a spy }); }); ``` ### When to Store Spies in Variables **Spies serve two purposes:** 1. **Mocking behavior** - Replace function implementation or return value 2. **Verification** - Assert the function was called with correct arguments **✅ Mocking only (no variable needed):** ```typescript it("should process user data", () => { // Arrange // Mock the API call to return test data, but don't store it jest.spyOn(API, "fetchUser").mockResolvedValue(mockUserData); // Act const result = processUserProfile(); // Assert // We're testing processUserProfile's logic, not that fetchUser was called expect(result.displayName).toBe("John Doe"); // No spy variable = no unused variable linter error }); ``` **✅ Mocking AND verification (store in variable):** ```typescript it("should call analytics when button is clicked", () => { // Arrange // Store the spy because we'll assert on it const trackEventSpy = jest .spyOn(Analytics, "trackEvent") .mockReturnValue(undefined); // Act userEvent.click(screen.getByRole("button")); // Assert // We're testing that the analytics call happens correctly expect(trackEventSpy).toHaveBeenCalledWith("button_click", { buttonId: "submit", }); }); ``` **Key point:** Only store the spy in a variable if you're going to assert on it. This avoids unused variable linter errors while still allowing you to verify calls when needed. ### Common Spy Patterns **Mock only (no variable):** ```typescript // When you only need to control the return value jest.spyOn(module, "functionName").mockReturnValue(mockValue); jest.spyOn(module, "asyncFunction").mockResolvedValue(mockValue); jest.spyOn(module, "asyncFunction").mockRejectedValue(new Error("Test error")); ``` **Mock and verify (store in variable):** ```typescript // When you need to assert the spy was called const spy = jest.spyOn(module, "functionName").mockReturnValue(mockValue); // ... later in Assert section: expect(spy).toHaveBeenCalledWith(expectedArgs); ``` **Spy with mock implementation:** ```typescript // Store only if you'll verify it was called const spy = jest.spyOn(module, "functionName").mockImplementation((arg) => { return processedValue; }); ``` ### Mocking Guidelines **DO:** - ✅ Use `jest.spyOn()` for mocking functions and tracking calls - ✅ Store spy return values in variables ONLY when you need to assert on them - ✅ Chain `.mockReturnValue()` or similar directly on `jest.spyOn()` when only mocking behavior - ✅ Keep mocks and spies inside test cases when possible - ✅ Use mocking and spies to isolate the code under test at boundaries with other code - ✅ Clean up spies after tests (Jest does this automatically with `clearAllMocks`) **DON'T:** - ❌ Never mock `console.error` - this hides real implementation issues - ❌ Never treat original functions as spies without `jest.spyOn()` - ❌ Never store spies in variables if you won't assert on them (causes unused variable linter errors) - ❌ Avoid mocking outside of tests, even if it means code duplication ### Hook Testing **✅ Use `renderHook`:** ```typescript import {renderHook} from "@testing-library/react"; // Direct for simple hooks const {result} = renderHook(() => useMyHook(params)); ``` ### User Interactions **✅ ALWAYS use `userEvent` for interactions:** ```typescript import userEvent from "@testing-library/user-event"; // ✅ DO: Use userEvent (realistic, includes focus/blur/typing) await userEvent.click(screen.getByRole("button")); await userEvent.type(screen.getByRole("textbox"), "hello"); // ❌ DON'T: Use fireEvent (low-level, less realistic) fireEvent.click(button); ``` ### Browser Behavior and jsdom Limitations **⚠️ jsdom does not fully implement all browser behaviors.** Common limitations include: `getBoundingClientRect()`, scroll positions, `offsetWidth`/`offsetHeight`, clipboard API, CSS animations, and Intersection/Resize Observers. **✅ Mock browser APIs when testing in unit tests:** ```typescript // Mock scrollIntoView const scrollIntoViewMock = jest.fn(); Element.prototype.scrollIntoView = scrollIntoViewMock; // Mock getBoundingClientRect jest.spyOn(Element.prototype, "getBoundingClientRect").mockReturnValue({ top: 100, left: 100, bottom: 200, right: 200, width: 100, height: 100, x: 100, y: 100, toJSON: () => {}, }); ``` **✅ Use Storybook interaction tests** for behavior that's difficult to mock accurately (scroll, layout, clipboard, complex focus management). See `.agents/skills/storybook/SKILL.md` for details. ### Element Selection **Query priority (use in this order):** 1. ✅ **Semantic queries** (best): `getByRole`, `getByLabelText`, `getByText` 2. ✅ **Test IDs** (fallback): `getByTestId` with `data-testid` attribute 3. ❌ **NEVER use** CSS selectors, direct DOM traversal, or raw node access **❌ NEVER access DOM nodes directly.** Always use Testing Library queries. Direct node access couples tests to implementation structure, not behavior. ```typescript // ✅ DO: Testing Library queries screen.getByRole("button", {name: /submit/i}); screen.getByLabelText("Email address"); screen.findByText("Welcome back"); screen.getByTestId("custom-widget");
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기