Writing Tests
Use this skill when the user asks to add tests to existing code, improve test coverage, or write tests for a specific file or module.
Steps
- Detect the test setup — check what's already configured:
# Check package.json for test runner cat package.json | grep -E "jest|vitest|mocha|playwright|cypress"Look for config files:vitest.config.ts,jest.config.ts,playwright.config.ts,.mocharc.*. Check for existing test files to understand the project's test patterns and conventions. - If no test runner exists — set one up:
npm install -D vitest @testing-library/react @testing-library/jest-domAdd atestscript topackage.json:{"test": "vitest run", "test:watch": "vitest"} - Analyze the target code — read the file(s) to test and identify:
- Public API: exported functions, classes, components, hooks - Code paths: conditionals, error handling, edge cases - Dependencies: external services, databases, APIs that need mocking - Side effects: file I/O, network calls, DOM mutations
- Create the test file — place it next to the source file or in a
__tests__/directory, matching the project's convention:
- src/utils/format.ts → src/utils/format.test.ts - src/components/Button.tsx → src/components/Button.test.tsx
- Write tests following this structure:
import {describe, it, expect, vi} from "vitest"; describe("functionName", () => {// Happy path it("returns formatted output for valid input", () => {...}); // Edge cases it("handles empty string", () => {...}); it("handles null/undefined input", () => {...}); // Error cases it("throws on invalid argument", () => {...}); // Boundary conditions it("handles maximum length input", () => {...});}); - Mock external dependencies — don't make real API calls or database queries in unit tests:
vi.mock("@/lib/db", () => ({query: vi.fn().mockResolvedValue([{id: 1, name: "test"}]),}));For React components, mock hooks that fetch data:vi.mock("@/hooks/useUser", () => ({useUser: () => ({user: {name: "Test"}, isLoading: false}),})); - Test React components with Testing Library:
import {render, screen, fireEvent} from "@testing-library/react"; it("renders the button and handles click", () => {const onClick = vi.fn(); render(<Button onClick={onClick}>Click me</Button>); fireEvent.click(screen.getByRole("button", {name: "Click me"})); expect(onClick).toHaveBeenCalledOnce();}); - Run the tests and verify they pass:
npm testIf any fail, fix the test or the code (depending on whether the test expectation or the implementation is wrong).
What to Test
- Always test: public API, error handling, edge cases (empty, null, zero, negative), state transitions, async behavior
- Skip testing: private implementation details, third-party library internals, simple getters/setters, type-only code
Notes
- Match the project's existing test style — if they use
test()instead ofit(), follow that. - Don't test implementation details — test behavior and outputs, not internal method calls.
- Use descriptive test names that explain the scenario: "returns 0 when cart is empty" not "test1".
- One assertion concept per test — multiple
expectcalls are fine if they verify the same behavior. - For async code, always
awaitthe result or useresolves/rejectsmatchers.