Approved Fixtures
Problem
Reviewing many tests becomes impractical when assertions are complex. Traditional assertions require reading code to understand what's being validated.
Pattern
Design tests around approval files that combine input and expected output in a domain-specific easy-to-validate format.
Validate the test execution logic once. After that, adding new test cases only requires reviewing fixtures.
Structure each approval file to contain:
- Input data (context, parameters, state)
- Expected output (results, side effects, API calls)
- Format adapted to your problem domain for easy scanning
The test runner reads fixtures, executes code, and regenerates approval files. Validation becomes a simple diff review.
This pattern works best for problems that have an intuitive visual representation that is straightforward to check, but can also be used for checking call sequences.
Example
The pattern adapts to different domains:
Testing a multi-step process with external service calls:
Create fixtures like checkout-with-discount.approved.md:
## Input
User: premium_member
Cart: [{product_id: "laptop-123", quantity: 1}, {product_id: "mouse-456", quantity: 1}]
Discount code: SAVE20
## Service Calls
POST /inventory/reserve
{"items": [{product_id: "laptop-123", quantity: 1}, {product_id: "mouse-456", quantity: 1}]}
Response: 200 {"reservation_id": "res_789"}
GET /pricing/calculate
{"items": [{product_id: "laptop-123", quantity: 1}, {product_id: "mouse-456", quantity: 1}], "user": "premium_member"}
Response: 200 {"subtotal": 1250, "discount": 250, "total": 1000}
POST /payment/process
{"amount": 1000, "reservation_id": "res_789"}
Response: 200 {"transaction_id": "txn_abc"}
## Output
Order: confirmed
Total: $1000
Email sent: order_confirmationSingle test reads all .approved.md files, executes flows, regenerates files with actual results. Review is scanning markdown diffs, not reading assertion code.
Testing visual algorithms:
Create fixtures like game-of-life-glider.approved.md:
## Input
......
..#...
...#..
.###..
......
## Result
......
......
.#.#..
..##..
..#...Test reads all game-of-life fixtures, computes next generation, verifies output matches. Adding new test cases is drawing ASCII patterns - trivially easy to validate correctness by eye.
Testing refactorings:
Create fixture pairs like inline-variable.input.ts and inline-variable.approved.ts:
This example uses two separate files. One for the input and one for the expected output. The header contains the command that generates the approved output.
Input file:
/**
* @description Inline variable with multiple usages
* @command refakts inline-variable "[{{CURRENT_FILE}} 8:18-8:21]"
*/
function processData(x: number, y: number): number {
const sum = x + y;
const result = sum * 2 + sum;
return result;
}Expected output file:
/**
* @description Inline variable with multiple usages
* @command refakts inline-variable "[{{CURRENT_FILE}} 8:18-8:21]"
*/
function processData(x: number, y: number): number {
const result = (x + y) * 2 + (x + y);
return result;
}