本文へ移動
cccskills
無料GitHub で公開

unit-testing

Write effective unit tests with Jest, Vitest, or pytest. Covers the test-doubles taxonomy (stub/spy/mock/fake), Arrange-Act-Assert, coverage threshold configuration and CI gating, snapshot testing, fake timers, and mutation testing with Stryker/mutmut. Use when: "unit test," "Jest," "Vitest," "pytest," "mock," "coverage threshold," "test doubles," "mutation testing," "fake timers," "snapshot test." Not for: interpreting coverage reports or finding coverage gaps — use coverage-analysis; AI generating the test code for you — use ai-test-generation; auditing existing tests for smells — use ai-qa-review; browser/component rendering assertions — use cypress-automation or visual-testing. Related: coverage-analysis, ci-cd-integration, ai-test-generation, shift-left-testing.

インストール方法を見る

含まれるファイル(2)

  • SKILL.md15.3 KB
  • references/patterns.md8.1 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

<objective> Write unit tests that fail when the code is wrong and pass when it is right — nothing weaker. A test that mocks every collaborator stays green while the integration is broken; the doubles taxonomy below stops that. A `coverageThreshold` typo (or the plural `coverageThresholds`, which Jest silently ignores) lets 40%-covered code ship on a green pipeline; the config and Verification sections below make the gate actually fire. This skill covers Jest, Vitest, and pytest: doubles, coverage gating, snapshots, fake timers, and mutation testing as a behavior check on top of coverage. </objective>

Discovery Questions

Check .agents/qa-project-context.md first — if it exists, use it and skip anything answered there.

  1. Framework: Jest, Vitest, or pytest? Check package.json or pyproject.toml. The runner decides config keys and mock APIs.
  2. Coverage tooling: Already configured? Look for jest.config.*, vitest.config.*, .nycrc, [tool.coverage]. Determines whether you add the gate or just tune it.
  3. Mocking strategy: Manual mocks, auto-mocking, or dependency injection? Check for __mocks__/ dirs or DI containers — this sets which doubles you reach for.
  4. Conventions: Co-location (*.test.ts next to source) or a __tests__/tests/ tree? Match what exists; don't introduce a third location.

Core Principles

1. Test behavior, not implementation. Verify what code does, not how. Refactoring internals should not break tests.

// Bad — implementation detail        // Good — observable behavior
expect(svc._cache.size).toBe(3);      expect(svc.getUser("abc")).toEqual({ id: "abc", name: "Alice" });

2. Fast, isolated, deterministic. No network/disk/DB. No shared mutable state. No uncontrolled Date.now() or Math.random() — freeze them with fake timers and seeded values.

3. Arrange-Act-Assert. One clear shape per test.

it("should apply discount for orders over $100", () => {
  // Arrange
  const order = createOrder({ subtotal: 150 });
  const svc = new DiscountService(0.1);
  // Act
  const result = svc.apply(order);
  // Assert
  expect(result.total).toBe(135);
});

4. One assertion concept per test. Multiple expect calls are fine when they verify the same concept.

5. Descriptive names. "should [behavior] when [condition]", not "test calculateTotal".


Framework-Specific Patterns

The full setup/teardown, mocking, spying, timer, in-source, and monorepo examples for each runner live in references/patterns.md. Below is what is current and what to reach for; copy the code from the reference.

Jest

Current is Jest 30.x (30.4.2, May 2026). Jest 30 added --collect-tests, jest.config.mts support, Temporal-aware fake timers, and clearMocksOnScope. If the code under test uses the Temporal API or time-zone logic, Jest 30's Temporal-aware fake timers remove a class of brittle setup.

Reach for: jest.mock() for module boundaries (jest.requireActual for partial mocks), jest.spyOn() to wrap a real method, jest.Mocked<T> for typed mocks, and jest.useFakeTimers() for time. See references/patterns.md § Jest.

Vitest

Same API as Jest, Vite-native. Stable: Vitest 4.1.x (June 2026); 5.0.0-beta is out (beta.3, May 2026). Vitest 4 added coverage.changed (changed-files-only coverage), mockThrow/mockThrowOnce, and a stable browser mode. Vitest 5 beta removes the sequential option and requires Node 22 / Vite 6.4 — wait for stable before adopting. Mock with vi.mock/vi.spyOn; the standout features are in-source testing (import.meta.vitest) and browser mode for component rendering. See references/patterns.md § Vitest.

pytest

Use fixtures + conftest.py (with yield for teardown), @pytest.mark.parametrize for data-driven cases, and monkeypatch for env/attr substitution. Prefer fixtures over setUp/tearDown methods — fixtures compose and isolate per test. See references/patterns.md § pytest.

Bun / Deno

bun test (Jest-compatible, no extra config) and deno test (native TS, permission flags) are reasonable defaults when your runtime is already Bun or Deno. Prefer Vitest/Jest for Node projects with deeper plugin ecosystems.


Mocking Taxonomy

Pick the simplest double that does the job. Most of the time that is a stub.

DoubleWhat it doesWhen to use
StubReturns canned data, no verificationControl a dependency's return value
SpyWraps real impl, records callsVerify calls without changing behavior
MockReplaces impl + records callsControl return AND verify interaction
FakeSimplified working impl (in-memory DB)Complex stateful dependencies

Rule of thumb: prefer stubs over mocks; reserve fakes for stateful dependencies; never call a real external API in a unit test. Only mock the external boundary (network, filesystem, DB, time) — let fast, deterministic internal collaborators run for real, or you get a suite that is green while the integration is broken. The four doubles in code: references/patterns.md § Test doubles.


Coverage

Configuration

Jest — the threshold key is coverageThreshold (singular). The plural coverageThresholds is not a Jest key: Jest ignores it silently, the gate never enforces, and CI stays green at 30% coverage. This is the single most common config bug.

// jest.config.js
module.exports = {
  coverageProvider: "v8",
  collectCoverageFrom: ["src/**/*.ts", "!src/**/*.{d,test,stories}.ts", "!src/**/index.ts"],
  coverageThreshold: { global: { branches: 80, functions: 80, lines: 80, statements: 80 } },
};

Vitest — set test.coverage.thresholds in vitest.config.ts with provider: "v8" (see references/patterns.md § Vitest for the full block).

pytest:

# pyproject.toml
[tool.coverage.run]
source = ["src"]
omit = ["src/**/test_*.py", "src/**/conftest.py"]
[tool.coverage.report]
fail_under = 80
show_missing = true
exclude_lines = ["pragma: no cover", "if TYPE_CHECKING:"]

Coverage types and what to gate on

TypeMeasuresBlind spots
BranchEvery if/else path taken?Misses value combinations
LineEach line executed?Misses untested branches in one line
StatementEach statement executed?Similar to line
FunctionEach function called?Nothing about correctness

Priority: Branch > Line > Statement > Function. Use 80% line as the baseline gate, not a vanity target, and weight branch coverage higher. Focus coverage on business logic, transformations, error paths, and edge cases; skip generated code, type definitions, barrel exports, trivial getters, and framework boilerplate.

For interpreting which uncovered lines matter and doing gap analysis, that's coverage-analysis, not this skill.

CI gate

Jest and Vitest exit non-zero when thresholds fail — that exit code IS the gate. pytest needs the flag explicitly:

- run: pytest --cov=src --cov-fail-under=80

Mutation Testing

Coverage tells you what code ran. Mutation testing tells you whether the tests would catch a bug. It makes small source changes (> → >=, true → false) and reruns the suite against each mutant. If the suite still passes, the mutant survived — your tests executed that logic but did not assert on it.

Stryker (JS/TS)

npm i -D @stryker-mutator/core @stryker-mutator/jest-runner  # or vitest-runner
// stryker.config.json  (Stryker's documented default; .mjs/.mts also load)
{
  "testRunner": "jest",
  "coverageAnalysis": "perTest",
  "mutate": ["src/**/*.ts", "!src/**/*.test.ts"],
  "thresholds": { "high": 80, "low": 60, "break": 50 },
  "reporters": ["html", "clear-text", "progress"]
}

Stryker's own defaults are { high: 80, low: 60, break: null } — break: null means no failing exit. Set break (e.g. 50) to make a low score fail CI. Run: npx stryker run.

mutmut (Python) — mutmut 3.x

mutmut 3 dropped the old CLI surface. Configure paths in a [mutmut] block, run, then review survivors in the TUI:

# setup.cfg  (or a [tool.mutmut] table in pyproject.toml)
[mutmut]
paths_to_mutate=src/
pip install mutmut          # 3.5.x
mutmut run                  # paths come from config, not a flag
mutmut browse               # interactive TUI: inspect and retest survivors
mutmut apply <mutant_id>    # write a survivor to disk to see what it changed

Avoid: mutmut run --paths-to-mutate=src/, mutmut results, and mutmut show 42 — that was the mutmut <3 surface. The --paths-to-mutate flag is gone (paths move to the [mutmut] config block) and results/show are replaced by browse/apply (mutmut 3.5.x, verified June 2026). Following the old commands errors out on a current install.

Interpreting scores

ScoreMeaning
90%+Strong — catching most logic changes
70–89%Decent — review survivors in critical paths
<70%Tests execute code but do not verify behavior

Run mutation testing on critical business logic, not the whole codebase (it is slow). Ignore equivalent mutants — logically identical code where no test could ever tell the difference.


Snapshot Testing

Use for: UI component render output, serialized data structures, CLI formatting — output where exact structure matters and is tedious to assert field-by-field.

Do not use for: frequently changing output (snapshot fatigue → rubber-stamp reviews), large snapshots (unreviewable), implementation details (CSS classes, internal IDs), or as a substitute for a targeted assertion when one specific value is what matters.

Prefer inline snapshots for small output (<20 lines) and property matchers (expect.any(String)) for dynamic fields like ids and timestamps. Always run CI with --ci so an unknown snapshot fails instead of being silently written and committed. Code: references/patterns.md § Snapshot testing.


Anti-Patterns

Testing private methods — Test through the public API. If a private method really needs its own tests, extract it to its own module with a public surface.

Mocking everything — Only mock external boundaries (network, filesystem, DB, time). A suite where every collaborator is mocked passes while the wiring between them is broken.

The plural coverageThresholds — Jest ignores it; the gate never fires; CI is green at any coverage. The key is coverageThreshold (singular). See Coverage above.

Faking all timers blindly — jest.useFakeTimers() / vi.useFakeTimers() with no allowlist can deadlock code awaiting a real microtask. Fake only what the test needs (doNotFake / toFake). See references/patterns.md § Jest timers.

Async test without await — a forgotten await makes the assertion never run and the test passes vacuously. Add expect.assertions(n) / expect.hasAssertions() to async tests so a missing assertion fails them.

Snapshot overuse — Use expect(x).toBe("active") for a specific value; reserve snapshots for structured output you can't assert field-by-field.

Non-descriptive names — Replace "works" with "should return empty array when no items match the filter".

Shared mutable state — Initialize in beforeEach, not at module scope:

// Bad: shared mutation               // Good: fresh per test
const items = [];                     let items: string[];
it("A", () => items.push("a"));       beforeEach(() => { items = []; });
it("B", () => {                       it("A", () => { items.push("a"); expect(items).toHaveLength(1); });
  items.push("b");                    it("B", () => { items.push("b"); expect(items).toHaveLength(1); });
  expect(items).toHaveLength(1); // FAILS
});

Verification

Prove the suite runs and the gate actually fails on under-coverage — the exact thing the coverageThreshold typo silently disables.

  1. Tests run and pass: npx jest (or vitest run, pytest -q) exits 0.
  2. The gate bites. Run coverage and confirm a non-zero exit when below threshold:
    npx jest --coverage --ci          # Jest/Vitest exit !=0 below coverageThreshold
    vitest run --coverage             # same for Vitest
    pytest --cov=src --cov-fail-under=80   # pytest exits !=0 below the floor
    
    Temporarily set a threshold above current coverage (e.g. 99) and confirm the command fails. If it exits 0, your threshold key is wrong (likely the plural coverageThresholds).
  3. Snapshots are safe in CI: the run uses --ci, so an unknown snapshot fails rather than being written. git status shows no new *.snap after a CI-mode run.

Done When

  • Coverage thresholds configured in jest.config.* (key coverageThreshold, singular), vitest.config.* (coverage.thresholds), or pyproject.toml (fail_under) AND verified to exit non-zero below threshold (Verification step 2)
  • Test files all live in the project's single chosen location (co-located OR __tests__/tests/) — git ls-files shows no ad-hoc test paths
  • External boundaries (HTTP, DB, time) are mocked and internal collaborators are not — grep finds no real network/DB clients constructed in test files
  • No test reaches outside the process boundary — suite passes with the network disabled and no test DB running
  • CI runs the test command with --ci (Jest/Vitest) so an unknown snapshot fails the build instead of being auto-written

Reference Files (in references/)

  • patterns.md — full runnable examples per framework: Jest setup/teardown, module/spy/timer mocks, async guards; Vitest config, in-source tests, concurrency, browser mode; pytest fixtures/parametrize/monkeypatch; Bun/Deno; the four test doubles; snapshot file/inline/property matchers.

Related Skills

  • coverage-analysis — interpreting coverage reports, finding meaningful gaps, mutation score as a first-class signal. Go there to read coverage; stay here to configure and gate it.
  • ci-cd-integration — test stages in pipelines, parallelization, caching, deployment gating.
  • ai-test-generation — when an AI writes the test code from a spec/PRD; this skill is for writing and structuring tests by hand.
  • ai-qa-review — auditing existing tests for hallucinated APIs, fabricated imports, and closed-loop tests.
  • shift-left-testing — pre-commit hooks, IDE integration, and TDD workflow around these tests.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Test for WCAG 2.2 AA compliance with axe-core + Playwright, keyboard navigation audits, screen reader testing, ARIA pattern validation, and legal compliance mapping (ADA, EAA, Section 508). Automated tools catch 30-40% of issues — this skill covers automated and manual testing together. Use when: "accessibility," "a11y," "WCAG," "screen reader," "axe," "keyboard navigation," "ARIA," "ADA compliance." Not for: cookie-consent/GDPR compliance — use compliance-testing; pixel-diff visual regression — use visual-testing. Related: playwright-automation, compliance-testing, visual-testing, ci-cd-integration.

日本語の概要は準備中です。原文の説明を表示しています。

petrkindlmann/qa-skills1722026年6月11日 更新

Goal-driven E2E testing where a browser agent (Playwright MCP / computer-use) reads a natural-language goal and explores the app via the accessibility tree to assert outcomes — no pre-written script. Covers when intent-driven beats scripted, making agent runs deterministic (pinned model, temperature 0, seeded data, bounded steps, explicit success assertion, snapshot-not-pixel), cost/latency control, the accessibility-tree-first interaction model, CI gating, and graduating a stable run into a scripted Playwright test. Use when: "agentic browser test," "goal-driven browser test," "let an agent explore the app," "natural-language E2E," "browser agent smoke test," "Playwright MCP test." Not for: Writing/maintaining deterministic scripted Playwright tests — that is playwright-automation. Testing your product's OWN LLM features — that is ai-system-testing. Related: playwright-automation, ai-system-testing, exploratory-testing, test-reliability, qa-project-context.

日本語の概要は準備中です。原文の説明を表示しています。

petrkindlmann/qa-skills1722026年6月11日 更新

Hybrid fingerprint + LLM pipeline for bug classification, deduplication, and ticket generation. Normalizes CI logs, creates stable fingerprints, clusters near-duplicates, then uses LLM for severity classification and ticket writing. Includes bug reporting templates and severity/priority matrix. Use when: "bug triage," "classify bugs," "failure analysis," "auto-classify," "CI failures," "bug report," "defect template." Not for: runtime self-healing of one flaky locator — use test-reliability. Not for: designing new tests from production telemetry — use observability-driven-testing. Related: qa-metrics, qa-dashboard, ci-cd-integration, qa-project-context.

日本語の概要は準備中です。原文の説明を表示しています。

petrkindlmann/qa-skills1722026年6月11日 更新

Review EXISTING test code for quality, smells, and testability issues. Detects test smells across six dimensions — readability, reliability, diagnostic value, design, AI-generated, and coverage — analyzes testability of application code, and backs the qualitative smells with mutation testing. Use when: "review my tests," "test quality audit," "test smells," "testability analysis," "are these tests any good." Not for: generating new tests — use `ai-test-generation`. Not for: testing AI features in your product — use `ai-system-testing`. Related: unit-testing, shift-left-testing, coverage-analysis, ai-test-generation.

日本語の概要は準備中です。原文の説明を表示しています。

petrkindlmann/qa-skills1722026年6月11日 更新

Test AI/LLM features that ship in your product. Covers prompt regression testing, response quality evaluation, tool-call validation, hallucination and RAG grounding checks, nondeterministic-output strategies, red-team/safety scans, eval frameworks, and agent-as-target injection (indirect injection via tool output / RAG / scan reports, self-propagating payloads, data exfiltration via an agent) plus a bundled detector for untrusted content. Use when: "test our LLM feature," "prompt regression test," "eval framework," "hallucination test," "RAG grounding," "nondeterministic output," "AI feature testing," "red-team our chatbot," "indirect prompt injection," "agent reading untrusted tool output," "production AI quality." Not for: using AI to generate your own test code — use ai-test-generation. Not for: classifying CI failures with AI — use ai-bug-triage. Not for: EU AI Act / GDPR conformity of an AI feature — use compliance-testing. Not for: canary/flag rollout of an AI feature — use testing-in-production. Related: ai-test-generation, ai-qa-review, api-testing, compliance-testing, security-testing, risk-based-testing, test-data-management.

日本語の概要は準備中です。原文の説明を表示しています。

petrkindlmann/qa-skills1722026年6月11日 更新

Use AI to write NEW test code from specs, PRDs, user stories, code diffs, bug reports, or OpenAPI specs. Staged pipeline: requirements extraction → risk analysis → coverage matrix → scenario generation → oracle design → test code → human review, with guardrails against hallucinated APIs and weak assertions. Use when: "generate tests from spec," "tests from PRD," "tests from user story," "auto-generate test cases," "AI write tests for me." Not for: testing AI/LLM features in your product — use ai-system-testing. Not for: auditing a pre-existing test suite you did not just generate — use ai-qa-review (Step 7 here only reviews tests THIS pipeline produced). Related: playwright-automation, unit-testing, api-testing, qa-project-context.

日本語の概要は準備中です。原文の説明を表示しています。

petrkindlmann/qa-skills1722026年6月11日 更新

petrkindlmann のスキルをすべて見る

このスキルの問題を報告する