agentleFS
lggarrisonlggarrison/agent-skills/skills/unit-testing/SKILL.md

unit-testing

Skill0 starsChanged 21 days agoMITView on GitHub

Writes, fixes, and reviews unit tests that check behavior, fail when the code is wrong, and give the same result on every run. Defaults to Vitest for JavaScript and TypeScript when a repo has no test runner yet. Covers proving a test can fail, test-first bug fixes, what to mock, removing nondeterminism, and fixing a failing test without weakening it. Use when adding or changing code that needs tests, fixing a bug, a unit test is failing or flaky, or reviewing tests in a diff.

20 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use database-testing, contract-testing and 3 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • petrkindlmannSkill

    database-testing

    Validate database integrity, test migrations forward and backward, verify schema constraints, manage seed data, detect migration drift, and identify query performance issues. Covers PostgreSQL, MySQL, MongoDB with Prisma, TypeORM, Drizzle, and SQLAlchemy, plus Testcontainers test databases. Use when: "database test," "migration test," "migration rollback," "rollback test," "data integrity," "SQL test," "schema validation," "seed data," "query performance," "Testcontainers." Not for: synthetic data generation/masking at scale — use test-data-management; Docker/IaC test-environment provisioning — use test-environments; SQL injection — use security-testing. Related: test-data-management, test-environments, security-testing, ci-cd-integration.

    Also in 3 projects that use this

  • petrkindlmannSkill

    contract-testing

    Implement consumer-driven contract testing with Pact-JS (v16). Covers consumer test writing, broker-driven provider verification, Pact Broker setup, can-i-deploy as a deployment gate, webhook-triggered verification, pending pacts, and schema-first vs consumer-first approaches (OpenAPI/Ajv, Schemathesis). Use when: "contract test," "Pact," "consumer-driven," "API contract," "provider verification," "can-i-deploy." Not for: stubbing or mocking a dependency to isolate a test — use service-virtualization; general REST/GraphQL endpoint assertions against your own API — use api-testing. Related: api-testing, service-virtualization, ci-cd-integration, test-environments.

    Also in 3 projects that use this

  • jarosser06Skill

    test-strategy

    Plans pytest test strategies for Drift's 90%+ coverage requirement using coverage analysis, edge case identification, and test suite design. Use when determining what tests to write, analyzing coverage gaps, or planning test strategies.

    Also in 4 projects that use this

  • Microsoft AzureSkill

    release-readiness

    Complete ODIN release documentation and validation checks before declaring user-visible work complete, preparing a release, or opening the Release-to-main pull request.

    Also in 3 projects that use this

  • Microsoft AzureSkill

    git-workflow

    Squad branching model: dev-first workflow with insiders preview channel

    Also in 4 projects that use this

The file

Show its rules and text

We split this skill into the rules it gives an agent

18 rules. 0 are common, 14 are distinctive, and 4 are very specific to this project.

  • 0

    Common

    Most of the 17 edited versions we read keep these.

  • 14

    Distinctive

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Use the repository's test runner, file layout, naming, helpers, and fixtures.

    line 19
  2. Very specific:

    When the repo has no unit test runner yet, use Vitest for JavaScript and TypeScript.

    line 26
  3. Distinctive:

    Test behavior through the unit's public interface: given these inputs, it returns this, throws that, or produces this side effect at a …

    line 32
  4. Distinctive:

    Write the cases the code actually handles or should handle.

    line 45
  5. Distinctive:

    Assert the exact result: Check the value, not that something is truthy.

    line 54
  6. Very specific:

    No logic in tests: No loops or conditionals that decide what to assert.

    line 58
  7. Distinctive:

    Before trusting a new test, see it fail for the right reason:

    line 64
  8. Distinctive:

    Bug fix: write the test first, run it against the unfixed code, and confirm it fails with the bug's symptom.

    line 66
  9. Distinctive:

    If the test still passes with the code broken, it isn't testing that code.

    line 71
  10. Distinctive:

    Prefer a small fake (an in-memory store, a fixed clock) over a mock that scripts every call.

    line 82
  11. Distinctive:

    Remove the usual causes:

    line 98
  12. Distinctive:

    Order: don't depend on the order of tests, of hash or set iteration, or of concurrent work.

    line 102
  13. Distinctive:

    Real I/O: no real network. Use a fresh temp directory per test for file work.

    line 106
  14. Very specific:

    Waiting: never sleep to wait for async work.

    line 107
  15. Distinctive:

    If the code is wrong, fix the code.

    line 117
  16. Distinctive:

    If the test is wrong (it encodes a mistaken expectation, or the behavior changed on purpose), change the test, and say in the commit why …

    line 118
  17. Distinctive:

    Never get to green by deleting or skipping a test, loosening an assertion, catching the error, or copying the actual output into the …

    line 121
  18. Distinctive:

    Before handing over tests, or when reviewing them:

    line 129

“Common” is measured: kept by at least 6 in 10 of the 17 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Unit testing

A unit test exists to fail when the code is wrong. One that passes no matter what the code does is worse than none, because it tells the next person the code is covered. Everything here serves two properties: the test fails for a real bug, and it gives the same answer on every run.

The sister skill e2e-testing covers tests that drive the whole system through a browser, a CLI, or an API. When a behavior can be checked at either level, check it here. Unit tests are faster, more precise, and less flaky.

Repo rules come first

Use the repository's test runner, file layout, naming, helpers, and fixtures. Read a few existing tests near the code before writing one, and match them. AGENTS.md, CONTRIBUTING.md, and the package scripts say how tests are run. Where they conflict with this skill, they win. Don't add a second test framework or assertion library.

Default tool

When the repo has no unit test runner yet, use Vitest for JavaScript and TypeScript. For other languages, use the ecosystem's standard runner, such as pytest for Python or go test for Go. Examples here use Vitest. The rules apply with any runner.

What to test

Test behavior through the unit's public interface: given these inputs, it returns this, throws that, or produces this side effect at a boundary. Don't test private helpers directly, and don't assert on internal call order or call counts unless the call is itself the behavior (a request sent, a message published).

For each behavior, think through the cases that tend to break:

  • empty, one, and many;
  • the boundaries of every range: zero, negatives, the maximum, off by one;
  • invalid input and every error path the code handles on purpose;
  • inputs that look the same but aren't: null and missing, 0 and "", Unicode, whitespace, time zones.

Write the cases the code actually handles or should handle. Don't pad the suite to raise a coverage number.

How to write one

  • One behavior per test, named for that behavior: rejects an expired token, not test validate 3. A failure message should say what broke without opening the file.
  • Arrange, act, assert. Build inputs in the test, or with a helper whose name says what it builds, so a reader sees the important values without jumping elsewhere.
  • Assert the exact result. Check the value, not that something is truthy. Check an error's type or message, not only that something was thrown.
  • Await everything async. A test that starts a promise and doesn't await it, or asserts inside a callback that never runs, passes without checking anything.
  • No logic in tests. No loops or conditionals that decide what to assert. Use the runner's parameterized tests for many cases: test.each in Vitest, @pytest.mark.parametrize in pytest, table tests in Go.

Prove it can fail

Before trusting a new test, see it fail for the right reason:

  • Bug fix: write the test first, run it against the unfixed code, and confirm it fails with the bug's symptom. Then fix the code and watch it pass.
  • New code: break the code on purpose (flip a condition, return early, change a constant), run the test, confirm it fails, then undo the change.

If the test still passes with the code broken, it isn't testing that code. Fix the test.

Mocks and fakes

Replace things at the edges of the system, not inside it:

  • Replace: the network, the clock, randomness, the file system when it's slow or shared, and third-party services.
  • Keep real: the unit under test, and the code it calls inside the same module or package. Mocking those tests the mock.

Prefer a small fake (an in-memory store, a fixed clock) over a mock that scripts every call. Where the code reads the time, a random value, or the environment, pass it in or use the runner's tools, rather than patching globals ad hoc. In Vitest that's vi.useFakeTimers() and vi.setSystemTime() for time, vi.stubEnv() for environment variables, vi.spyOn() for a single function, and vi.mock() for a module at a boundary. Each needs its own cleanup after every test, because one doesn't undo the others:

  • fake timers: vi.useRealTimers();
  • environment: vi.unstubAllEnvs(), or unstubEnvs: true in the config;
  • spies: vi.restoreAllMocks(), or restoreMocks: true in the config;
  • vi.mock() applies to the whole test file, so put tests that need the real module in another file.

Same answer every run

A test that sometimes fails costs everyone time and teaches people to ignore red builds. Remove the usual causes:

  • Time: freeze the clock, and set the time zone and locale when output depends on them.
  • Randomness: seed it, or inject the source.
  • Order: don't depend on the order of tests, of hash or set iteration, or of concurrent work. Sort before comparing when order isn't the behavior.
  • Shared state: reset globals, singletons, module caches, environment variables, and temp files after every test. Each test must pass alone and in any order.
  • Real I/O: no real network. Use a fresh temp directory per test for file work.
  • Waiting: never sleep to wait for async work. Await it, or advance fake timers.

To check a fix for a flaky test, run that test many times in a row, and in random order if the runner supports it, before calling it fixed. In Vitest, give the test a repeats option and run the suite with --sequence.shuffle.

When a test fails

Find out which is wrong, the code or the test, before changing either.

  • If the code is wrong, fix the code. The test did its job.
  • If the test is wrong (it encodes a mistaken expectation, or the behavior changed on purpose), change the test, and say in the commit why the old expectation no longer holds.
  • Never get to green by deleting or skipping a test, loosening an assertion, catching the error, or copying the actual output into the expected value without checking it. Delete a test only when the behavior it checks was removed on purpose.
  • Snapshot updates get the same care: read the snapshot diff, and accept it only when every change is intended.

Checklist

Before handing over tests, or when reviewing them:

  • Each new test was seen to fail, against the bug or a deliberate break.
  • Tests check behavior through the public interface, not internals.
  • Assertions are exact, and every async call is awaited.
  • Only boundaries are mocked.
  • No real time, randomness, network, or shared state. Each test passes alone.
  • No test was skipped, deleted, or weakened to get green.
  • The single test, then the whole suite, pass with the repo's own command.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.