This document describes the high-level architecture of ApprovalTests.Java. If you want to familiarize yourself with the codebase, this is a good place to start.
ApprovalTests is a library for unit testing that replaces hand-written assertions with a "golden master" approach:
test output is saved to a file, and future test runs are compared against that saved snapshot.
When a test produces new output, a Writer serializes it to a .received. file.
A Namer determines the file paths for both .received. and .approved. files (derived from the test class and method names).
An Approver compares the two files, and if they differ, a Reporter shows the difference (typically by launching a diff tool).
The user's main entry point is Approvals.verify().
The project is a multi-module Maven build. The two library modules that ship to users are approvaltests-util and approvaltests.
The two -tests modules and counter_display are never published.
approvaltests-util Shared utilities (no dependency on approvaltests)
approvaltests Core approval testing library (depends on approvaltests-util)
approvaltests-util-tests Tests for approvaltests-util
approvaltests-tests Tests for approvaltests
counter_display Internal developer tool
This module contains general-purpose utilities under two root packages:
-
com.spun.util— Helper classes for strings, arrays, JSON, IO, reflection, and more. These are used heavily throughoutapprovaltestsbut have no dependency on it. Key types:ObjectUtils,StringUtils,ArrayUtils,JsonUtils,FileUtils(inio/). -
org.lambda— A lightweight functional programming library providingFunction0–FunctionN,Action0–ActionN, andQuery(a collection-querying API used in place of Java Streams throughout the codebase). -
org.packagesettings— A small framework for hierarchical configuration viaPackageSettings.javafiles placed in Java packages.PackageLevelSettingswalks the package hierarchy to resolve settings.
This is the core library. Everything lives under org.approvaltests. The main concepts map to subpackages:
Defines the key interfaces and types that the rest of the system plugs into:
ApprovalWriter— interface for writing test output to a.received.file.ApprovalFailureReporter— interface invoked when a test fails (received ≠ approved).Scrubber— interface for transforming output before comparison (e.g., replacing dates or GUIDs with deterministic placeholders).Options— the main configuration object, carrying a scrubber, reporter, file extension, namer, writer, and comparator. Immutable-style builder viawith*()methods.Verifiable— interface for objects that know how to verify themselves.
Implementations of ApprovalWriter. ApprovalTextWriter handles the common text case; others handle binary files, images, XML, AWT components, and directory-to-directory comparisons. ApprovalWriterFactory / DefaultApprovalWriterFactory control which writer Options uses by default.
Implementations of ApprovalNamer. StackTraceNamer is the default — it inspects the call stack to derive ClassName.methodName and the source directory. NamerFactory and NamerFactoryForOptions provide labelling (e.g., parameterized test names). ApprovalResults offers static helpers for customizing the namer from within a test.
ApprovalApprover is the interface for the approve/report lifecycle (approve(), reportFailure(), cleanUpAfterSuccess()). FileApprover is the standard implementation — it orchestrates the core verify loop: write the received file, compare it with the approved file, and report on failure. ApprovalTracker detects duplicate verify() calls in a single test.
A large family of ApprovalFailureReporter implementations. Each one knows how to launch a specific diff tool (Beyond Compare, IntelliJ, VS Code, etc.) or perform a specific action (clipboard, JUnit assert, auto-approve).
DiffReporter— the default, tries a platform-appropriate list of diff tools.FirstWorkingReporter— tries reporters in order until one succeeds.UseReporter— annotation for selecting a reporter at the test/class/package level.ReporterFactoryresolves the active reporter by checking@UseReporterannotations, thenPackageSettings, then falling back toDiffReporter.- Platform subdirectories (
windows/,macosx/,linux/,intellij/) contain OS-specific diff tool integrations.
Implementations of Scrubber: DateScrubber, GuidScrubber, RegExScrubber, NormalizeSpacesScrubber, and Scrubbers (a static utility combining them).
CombinationApprovals — verifies a function against all combinations of input parameters. The pairwise/ subpackage provides pairwise combination reduction.
An alternative approval mode where the expected value lives inside the test source code (as a string literal) rather than in a separate .approved. file. Key types: InlineJavaReporter, InlineComparator, InlineOptions.
AwtApprovals — approval testing for graphical AWT/Swing components and animated GIF sequences.
LegacyApprovals — utilities for approval-testing hard-to-test legacy code via lock-down tests over input permutations.
TestCommitRevertRunner — a test runner for the TCR (Test && Commit || Revert) workflow.
JUnit 5 extensions for approval tests.
Approvals— the primary public API.verify(),verifyAll(),verifyAsJson(), and friends. All overloads ultimately funnel throughverify(ApprovalWriter, Options).JsonApprovals,JsonJacksonApprovals,JsonJackson3Approvals,JsonXstreamApprovals— JSON-specific verify methods using different serialization libraries.StoryBoard/MarkdownStoryBoard/StoryBoardApprovals— for verifying sequences of state changes (e.g., Game of Life frames).ReporterFactory— resolves which reporter to use for a given test, walking annotations and package settings.ApprovalSettings/ApprovalUtilities— global settings and helpers.
A call to Approvals.verify("hello") proceeds roughly as follows:
Optionscreates anApprovalTextWriter(via itsApprovalWriterFactory).Approvalsobtains anApprovalNamer(by default aStackTraceNamerthat reads the call stack).Optionsscrubs the output through itsScrubber(default isNoOpScrubber).- A
FileApproveris constructed with the writer, namer, and comparator. FileApprover.approve()writes the.received.file and compares it with the.approved.file.- If they differ,
ReporterFactoryresolves the activeApprovalFailureReporterand callsreport(). - The reporter (e.g.,
DiffReporter) launches a diff tool or throws an assertion error.
Tests mirror the main source structure. Approved files (.approved.txt, .approved.json, etc.) live alongside the test source files and are checked into version control. Received files (.received.*) are generated at test time and should not be committed.
Test files are in approvaltests-tests/src/test/java/org/approvaltests/ and approvaltests-util-tests/src/test/java/.