Unit Testing Rules
Sets expectations for focused unit tests, readable assertions, and meaningful behavior coverage.
Scenario
You maintain a TypeScript application where business logic is covered by unit tests using Vitest.
The team wants tests to focus on observable behavior rather than implementation details.
Coding agents should know:
- when unit tests are expected;
- where tests belong;
- what should be mocked;
- what should not be tested directly;
- how to validate changes.
Repository Structure
application/
├── src/
│ ├── pricing/
│ │ ├── calculate-discount.ts
│ │ └── calculate-total.ts
│ └── users/
├── tests/
│ ├── pricing/
│ │ ├── calculate-discount.test.ts
│ │ └── calculate-total.test.ts
│ └── users/
├── package.json
└── AGENTS.md
AGENTS.md
# Project Instructions
## Unit Tests
- Add or update unit tests when business behavior changes.
- Test observable behavior rather than private implementation details.
- Keep tests focused on one behavior or closely related set of behaviors.
- Cover important success, failure, and boundary cases.
## Test Location
- Keep unit tests under `tests/` using the same domain structure as `src/`.
- Follow existing test naming conventions.
## Mocking
- Mock external boundaries when isolation is useful.
- Do not mock the function or behavior being tested.
- Avoid mocking internal implementation details only to make a test easier to write.
## Bug Fixes
- When fixing a reproducible bug, add a regression test when practical.
## Validation
During development, run the relevant unit tests.
Before completing substantial changes, run:
- `pnpm lint`
- `pnpm typecheck`
- `pnpm test`
What This Does
This gives the agent a testing philosophy rather than merely saying:
- Write tests.
For example, suppose the project contains:
export function calculateDiscount(total: number, isPremium: boolean) {
if (isPremium && total >= 100) {
return total * 0.1;
}
return 0;
}
Useful tests focus on behavior:
expect(calculateDiscount(150, true)).toBe(15);
expect(calculateDiscount(50, true)).toBe(0);
expect(calculateDiscount(150, false)).toBe(0);
The tests describe what the function should do.
What This Does NOT Do
The instructions do not require tests for every line or every private helper.
For example, suppose calculateDiscount internally calls:
isEligibleForPremiumDiscount()
A test does not necessarily need to assert that this helper was called.
The important behavior is the resulting discount.
Likewise, the file does not require mocking everything.
Excessive mocking can produce tests that verify an artificial implementation rather than real behavior.
Why These Instructions Matter
An agent asked to add functionality may create tests that are technically present but provide little protection.
For example:
expect(calculateDiscount).toBeDefined();
This test passes but says almost nothing about business behavior.
Another weak pattern is testing implementation details:
expect(mockEligibilityHelper).toHaveBeenCalledOnce();
That test may break during a harmless refactor even though the observable behavior remains correct.
The instruction:
- Test observable behavior rather than private implementation details.
helps keep tests valuable during future refactoring.
Key Decisions
Test behavior
Ask:
What should callers observe?
rather than:
Which internal functions should execute?
Cover meaningful cases
Useful unit tests commonly include:
normal case
boundary case
failure or invalid case
important business exception
Not every possible input requires its own test.
Mock boundaries deliberately
External APIs, clocks, random values, or expensive infrastructure may be reasonable mocking targets.
Internal business functions usually should not be mocked merely to isolate every function from every other function.
Follow existing test organization
Agents should extend the repository's current testing conventions rather than introduce a parallel structure.
When to Use This Pattern
Use this pattern when:
- business logic can be tested independently;
- unit tests provide fast feedback;
- implementation details change more frequently than behavior;
- excessive mocking is a risk;
- agents need clearer guidance than simply "add tests."
Unit tests should make behavior safer to change, not make refactoring harder.