claude/ai-resources-plugin/skills/write-tests/SKILL.md
Write automated tests. Guides test selection, mocking strategy, and writing tests that verify behavior over implementation.
npx skillsauth add amhuppert/my-ai-resources write-testsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Tests exist to provide confidence that production code works correctly. Evaluate every testing decision through that lens: does this test increase real confidence, or does it create maintenance burden without meaningful coverage? A good test asserts observable behavior, exercises real production code paths, and would catch a real regression — it breaks when something is actually wrong, not when internals are refactored.
Not every piece of code needs a test — weigh value against maintenance cost.
Write tests for:
Skip or minimize tests for:
The deciding question: if this test breaks, does that indicate a genuine problem or just a refactor? Behavior tests ("when X happens, Y results") are durable. Structure tests ("function calls A then B") break on every internal change, creating noise without catching bugs.
Every function, component, or service has a public contract: its props, arguments, return values, and whatever consumers need to know. Tests should interact only through that contract — tests that rely on implementation knowledge break when the code is refactored, not when it is wrong.
Test (the contract):
Don't test (implementation details):
Follow this hierarchy — prefer options higher in the list:
jest.mock) — Only when a library performs side effects that cannot be controlled through injection (file system, native modules, global singletons, network, timers, browser APIs in Node). Prefer the real library when it is pure, fast, and deterministic. Even when a mock is warranted, wrap the side-effecting code in a thin injectable adapter and confine jest.mock to the adapter's own test file.jest.mock on your own modules — Don't. See below.jest.mock on an internal module couples the test to implementation details: configuring the mock requires knowing how the module is used internally — what it returns, when it is called, what shape the data takes. The resulting test verifies the mock, not the production code: it proves only that the mock was configured correctly and returns what it was told to return. Warning signs:
jest.mock() call on an own module — even one indicates the code lacks an injection pointIf a test would require mocking an own module, that is a design problem in the production code, not a testing problem — see "If the Code Is Not Testable" below.
Injected test doubles are fine and are not mocking in this problematic sense:
jest.fn() passed as a parameter or through contextWhen the production code supports DI, test setup looks like:
const mockUserService: UserService = {
getUser: jest.fn().mockResolvedValue(testUser),
updateUser: jest.fn().mockResolvedValue(updatedUser),
deleteUser: jest.fn(),
};
render(
<ServiceContext.Provider value={{ userService: mockUserService }}>
<ComponentUnderTest />
</ServiceContext.Provider>
);
No jest.mock calls, no module patching — the mock is a plain object satisfying an interface.
Every assertion should express "when X happens, Y results" — not "function calls A then B", unless the call sequence is itself part of the public contract (e.g., verifying an event was emitted).
Behavioral (durable):
const result = calculateDiscount({ total: 100, memberTier: "gold" });
expect(result).toBe(85);
Structural (fragile):
calculateDiscount({ total: 100, memberTier: "gold" });
expect(internalLookupTable.get).toHaveBeenCalledWith("gold");
expect(applyDiscount).toHaveBeenCalledBefore(formatResult);
For components, assert on rendered output and observable effects, not on which internal hook was called or which child component received which prop.
Before finalizing, ask: if the production code were replaced with a function that just returns the mock's value directly, would this test still pass? If yes, the test exercises the mock, not the code — typical forms are the mock echo (mock returns X, test asserts X) and interaction-only tests (assertions solely about what mocked functions were called with). Rewrite the test to verify a meaningful transformation — the production code transforms, filters, combines, or validates data and the test checks that result — or delete it.
Prefer integration tests when dependencies are cheap (in-memory databases, pure libraries, lightweight services) — a single integration test often provides more confidence than a dozen heavily-mocked unit tests. Drop to unit tests when:
Error handling is part of the public contract. Exercise error conditions using real error inputs — throwing errors from injected test doubles, invalid arguments, or simulated failures through injected clients. Avoid asserting exact error message strings unless the message is part of the contract.
When writing a test exposes a testability problem (hard-coded dependencies, no injection point, tight coupling to a global), stop and raise the design issue before adding brittle mocks — a jest.mock-heavy test locks in the bad design and makes future refactoring harder. Recommend one of:
For legacy code without DI, recommend incremental refactoring toward an injectable architecture rather than papering over with mocks.
development
Debug a running web app via the web-debugger SDK: app logs, application state, runtime snapshots, React state, query cache.
development
Thoroughly understand a software development objective before implementation: research, identify ambiguities, ask clarifying questions. Use before starting implementation of a non-trivial or ambiguously specified feature, or when requirements leave open design decisions.
development
Locate the on-disk Claude Code transcript file (.jsonl under ~/.claude/projects/) for the current or a specified conversation.
development
Reflect on codebase navigation effectiveness at end of conversation. Surfaces dead ends, inefficiencies, missing context. Does not write files — pair with /kiro:steering-custom to persist.