toolhq.io

skills / code quality and testing

write-tests

Tests that fail when behaviour breaks, rather than restating the implementation back at itself.

SKILL.md

Save as .claude/skills/write-tests/SKILL.md in a repository, or under ~/.claude/skills/write-tests/SKILL.md to have it everywhere. The copy button takes the whole file, frontmatter included.

---
name: write-tests
description: Write tests that catch real breakage instead of restating the implementation. Use when asked to add tests, improve coverage, write unit or integration tests, or test a new function or endpoint.
---

# Write tests

A test earns its place by failing when the behaviour breaks. A test that passes
whatever the code does is worse than none, because it costs maintenance and
buys confidence that is not real.

## Before writing anything

Ask what this code is supposed to guarantee. Test that, not the lines.

If you cannot state the guarantee in a sentence, the test you are about to
write will assert the implementation back at itself.

## What to test, in order

1. **The happy path**, once. Not five variations of it.
2. **The boundaries.** Empty, one, many, the maximum, zero, negative, the first
   and last element. Most bugs are here.
3. **The error paths.** Invalid input, a missing dependency, a timeout. Assert
   the specific failure, not merely that something was raised.
4. **The bug you just fixed.** Every fix gets a test that fails without it.
   This is the highest value test in any suite because you know it catches a
   real defect.

## What not to test

- Code you do not own. Testing that the standard library sorts is not a test of
  your code.
- Getters, setters and pass through wrappers with no logic.
- The same logic at three levels. Pick the level where a break would matter.
- Implementation details: private methods, call counts, the order of internal
  steps. These fail on every refactor and catch nothing.

Coverage is a diagnostic, not a target. Chasing a number produces tests for the
code that was easiest to reach, which is rarely the code most likely to break.

## The rules that keep a suite usable

**One reason to fail.** A test that asserts six unrelated things tells you
little when it goes red.

**Name it after the behaviour.** `rejects_an_expired_token` tells you what
broke from the test report alone. `test_auth_2` does not.

**No logic in the test.** No loops, no branches, no computing the expected
value with the same function under test. Write the expected value literally,
even when repetitive. A test with a bug in it is undetectable.

**Deterministic.** No real clock, no real network, no random without a fixed
seed, no dependence on test order. An intermittent test gets ignored, then the
whole suite does.

**Arrange, act, assert**, visibly separated. A reader should see the setup, the
one call, and the check.

## Mocking

Mock at the boundary: the network, the clock, the filesystem, the payment
provider. Do not mock the thing you are testing, and do not mock your own
internal functions to make a test pass, because then you are testing the mock.

A test with five mocks is usually telling you the code has too many
dependencies. That is a finding worth reporting, not working around.

## Verify the test itself

Before you call it done, break the code deliberately and confirm the test
fails. A test that has never been seen to fail has not been shown to work.

## Report back

1. What guarantee each test protects.
2. Anything you chose not to test, and why.
3. Anything untestable as written, with the change that would fix it.

what it needs

Nothing. This skill is instructions only, so it works in any agent that reads SKILL.md with nothing installed and no account. Save the file and it is live in the next session.

the instructions, in full

Write tests

A test earns its place by failing when the behaviour breaks. A test that passes whatever the code does is worse than none, because it costs maintenance and buys confidence that is not real.

Before writing anything

Ask what this code is supposed to guarantee. Test that, not the lines.

If you cannot state the guarantee in a sentence, the test you are about to write will assert the implementation back at itself.

What to test, in order

  1. The happy path, once. Not five variations of it.
  2. The boundaries. Empty, one, many, the maximum, zero, negative, the first and last element. Most bugs are here.
  3. The error paths. Invalid input, a missing dependency, a timeout. Assert the specific failure, not merely that something was raised.
  4. The bug you just fixed. Every fix gets a test that fails without it. This is the highest value test in any suite because you know it catches a real defect.

What not to test

Coverage is a diagnostic, not a target. Chasing a number produces tests for the code that was easiest to reach, which is rarely the code most likely to break.

The rules that keep a suite usable

One reason to fail. A test that asserts six unrelated things tells you little when it goes red.

Name it after the behaviour. rejects_an_expired_token tells you what broke from the test report alone. test_auth_2 does not.

No logic in the test. No loops, no branches, no computing the expected value with the same function under test. Write the expected value literally, even when repetitive. A test with a bug in it is undetectable.

Deterministic. No real clock, no real network, no random without a fixed seed, no dependence on test order. An intermittent test gets ignored, then the whole suite does.

Arrange, act, assert, visibly separated. A reader should see the setup, the one call, and the check.

Mocking

Mock at the boundary: the network, the clock, the filesystem, the payment provider. Do not mock the thing you are testing, and do not mock your own internal functions to make a test pass, because then you are testing the mock.

A test with five mocks is usually telling you the code has too many dependencies. That is a finding worth reporting, not working around.

Verify the test itself

Before you call it done, break the code deliberately and confirm the test fails. A test that has never been seen to fail has not been shown to work.

Report back

  1. What guarantee each test protects.
  2. Anything you chose not to test, and why.
  3. Anything untestable as written, with the change that would fix it.