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.
Write automated tests that provide real confidence in production code behavior with minimal maintenance burden. Focus on behavioral assertions, proper dependency injection, and avoiding over-mocking.
Tests exist to provide confidence that production code works correctly. Evaluate every testing decision through this lens: does this test increase real confidence, or does it create maintenance burden without meaningful coverage?
A good test:
A bad test:
Not every piece of code needs a test. Before writing, weigh value against maintenance cost.
Write tests for:
Skip or minimize tests for:
Evaluation criteria:
Every function, component, or service has a public contract: its props, arguments, return values, and whatever details consumers need to know. Tests should only interact through that public contract.
Public contract (test this):
Implementation details (don't test this):
Tests that rely on implementation detail knowledge break when the implementation is refactored, not when something is actually wrong. This is maintenance burden without added confidence.
Connection to mocking: jest.mock module replacement inherently requires the test to know about implementation details. To set up the mock correctly, the test author must understand how the mocked module is used internally — what it returns, when it's called, what shape the data takes. This couples the test to internals, making it brittle and divergent from production behavior.
Follow the mocking hierarchy — prefer options higher in the list, avoid lower ones.
jest.fn(), mock service satisfying the interface).jest.mock) — Use 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).jest.mock — Almost always a design smell. If internal code needs to be replaced, the code likely lacks proper DI.What counts as a legitimate test double (fine, not "mocking"):
jest.fn() passed as a parameter or through contextWhat counts as problematic mocking:
jest.mock('./internal/service') replacing an entire internal modulejest.mock configured so the test never runs the real production code pathSigns that jest.mock is covering for missing DI:
Prefer the real library when:
Even when jest.mock on a third-party library is warranted, prefer wrapping the side-effecting code in a thin adapter that can be injected. Confine jest.mock to the adapter's own test file rather than spreading it across the codebase.
When 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.
If the production code does not yet support injection and would require jest.mock on own modules, flag this as a testability problem before writing the test. Recommend refactoring toward injection rather than adding brittle mocks.
Every assertion should express "when X happens, Y results." Avoid "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.
What good tests look like:
jest.mock is reserved for third-party side effects. Injected test doubles (mock data, jest.fn() passed as parameters, mock objects via context) handle everything else.Aggressive mocking can create the illusion of strong test coverage while providing minimal real confidence. When a test mocks most dependencies and then asserts the result, it may only verify that:
None of these assertions prove the production code works.
Before finalizing the test, ask: "If I replaced the production code with a function that just returns the mock's value directly, would this test still pass?"
If yes, the test is exercising the mock, not the code. Rewrite it to cover meaningful transformation, or delete it.
Anti-patterns to watch for:
Over-mocking indicators:
jest.mock() calls on internal/own modules (even one is a smell)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.
jest.mock is only used for third-party side effects, never internal modulesreturn mockValue would cause this test to failtools
Use when picking or vetting a keyboard shortcut on macOS. Triggers include "what hotkey should I use for X", "is `<combo>` available", "does this shortcut conflict", "recommend a keybinding for…", "check `<combo>` against my setup", "pick a hotkey for…", or any mention of choosing/binding/changing a shortcut in WezTerm, tmux, Zed, Chrome, Claude Code, or macOS. Determines whether a proposed combo collides with OS-reserved bindings, app defaults, or the user's customizations, and recommends ergonomic alternatives when needed.
development
Detect and remove dead code with knip. Use when the user asks to "run knip", "find unused files", "find unused exports", "find unused dependencies", "clean up dead code", "remove dead code", "set up knip", "configure knip", "knip.json", "knip false positive", "knip CI", or mentions a `knip` config, dependency bloat, bundle bloat from unused imports, or tree-shaking unused exports. Covers the configuration-first workflow, confidence-gated deletion, framework-specific gotchas (Next.js 15+, Tailwind, Storybook, Jest, Bun's test runner and `bun build --compile`), monorepos, CI integration, and performance tuning.
tools
This skill should be used when the user asks to "set up react-scan", "install react-scan", "diagnose React re-renders", "find unnecessary renders", "find unstable props", "automate React render checks with Playwright", "react-scan + playwright", "measure component renders programmatically", "check why a React component is slow", or mentions React rendering issues, slow React interactions, render counts, or component-level perf attribution. Covers install across Next.js/Vite/Remix/script-tag/browser-extension, the lite headless API for CI, and the canonical render-attribution → fix → validate loop driven through Playwright.
documentation
This skill should be used when integrating source material into a knowledge base, including when the user asks to "integrate this document into the knowledge base", "add this transcript to the memory bank", "ingest this document", "update the knowledge base", "analyze a new source document", or "sync current-state docs with this source".