Approval testing
Some outputs resist hand-written assertions: a rendered invoice, a generated
SQL query, a serialized object graph, an ASCII report. Spelling out every
field in assertEquals is brittle to write and unreadable afterward.
Approval testing inverts the flow: the code prints its result, a human reads
it once and approves it, and the framework guards that approved value from
then on.
Method
- Emit output in a stable, diffable format. Serialize to sorted JSON,
pretty-printed text, or a canonical string. Unstable key order or trailing
whitespace turns every run into a false diff, so normalize before you
write.
- Split into received and approved files. The run writes
invoice.received.txt; the test compares it to invoice.approved.txt.
A missing or mismatched approved file fails the test. Tools like
ApprovalTests, Verify, syrupy, or insta manage this pair for you.
- Review the received file like a pull request. Read it line by line and
decide whether every value is correct. This review is the actual
assertion; skimming it defeats the method. Wrong output you approve is
wrong output you have now locked in.
- Approve by promoting received to approved. Rename or run the approve
command, then commit the approved file. It now lives in version control as
the reviewed specification of the output.
- Wire a readable diff reporter. Configure the framework to open a diff
viewer on mismatch so the change is obvious at a glance. A wall of "line
417 differs" trains people to re-approve blindly.
- Scrub nondeterminism before comparison. Replace timestamps, GUIDs,
temp paths, and random ids with fixed sentinel values. Anything that changes
run to run must be masked, or the approval never stabilizes.
- Review approved-file changes in code review. A diff to an
.approved
file in a pull request is a behavior change. Treat an unexplained one as a
red flag, the same as a suspicious source edit.
Litmus tests
- When output changes, does the diff show a human exactly what moved and let
them accept or reject it in seconds?
- Is every run's received file byte-identical given the same input, with all
volatile values masked?
- Would a reviewer notice an incorrect value slipping into an approved file
during code review?
Boundaries
Approval testing shines when output is large and recognition is easy; for a
rule expressible in one line, a plain assertion is clearer and defer to
unit-test-design. For screenshots specifically, use visual-regression-testing,
which handles image thresholds this text-oriented method cannot.
1---2name: approval-testing3description: Assert hard-to-specify output by reviewing a human-readable snapshot once, approving it, then failing on any later diff. Use when the correct result is easy to recognize but tedious to write as explicit assertions.4---56# Approval testing78Some outputs resist hand-written assertions: a rendered invoice, a generated9SQL query, a serialized object graph, an ASCII report. Spelling out every10field in `assertEquals` is brittle to write and unreadable afterward.11Approval testing inverts the flow: the code prints its result, a human reads12it once and approves it, and the framework guards that approved value from13then on.1415## Method16171. **Emit output in a stable, diffable format.** Serialize to sorted JSON,18 pretty-printed text, or a canonical string. Unstable key order or trailing19 whitespace turns every run into a false diff, so normalize before you20 write.212. **Split into received and approved files.** The run writes22 `invoice.received.txt`; the test compares it to `invoice.approved.txt`.23 A missing or mismatched approved file fails the test. Tools like24 ApprovalTests, Verify, syrupy, or insta manage this pair for you.253. **Review the received file like a pull request.** Read it line by line and26 decide whether every value is correct. This review is the actual27 assertion; skimming it defeats the method. Wrong output you approve is28 wrong output you have now locked in.294. **Approve by promoting received to approved.** Rename or run the approve30 command, then commit the approved file. It now lives in version control as31 the reviewed specification of the output.325. **Wire a readable diff reporter.** Configure the framework to open a diff33 viewer on mismatch so the change is obvious at a glance. A wall of "line34 417 differs" trains people to re-approve blindly.356. **Scrub nondeterminism before comparison.** Replace timestamps, GUIDs,36 temp paths, and random ids with fixed sentinel values. Anything that changes37 run to run must be masked, or the approval never stabilizes.387. **Review approved-file changes in code review.** A diff to an `.approved`39 file in a pull request is a behavior change. Treat an unexplained one as a40 red flag, the same as a suspicious source edit.4142## Litmus tests4344- When output changes, does the diff show a human exactly what moved and let45 them accept or reject it in seconds?46- Is every run's received file byte-identical given the same input, with all47 volatile values masked?48- Would a reviewer notice an incorrect value slipping into an approved file49 during code review?5051## Boundaries5253Approval testing shines when output is large and recognition is easy; for a54rule expressible in one line, a plain assertion is clearer and defer to55unit-test-design. For screenshots specifically, use visual-regression-testing,56which handles image thresholds this text-oriented method cannot.