phpunit-snapshot-assertions: testing PHP without writing the expectations
A way to test without writing actual test cases
At a glance
- What is it?
- spatie/phpunit-snapshot-assertions lets a PHPUnit test record the value it produced on the first run and compare against that record on every later run, so you get a regression test without hand writing the expected output. It ships ten assertion types and a wrapper binary for updating snapshots.
- Who is it for?
- The package suits a PHP or Laravel team that has output shaped like JSON, HTML, XML or YAML and no good way to hand write the expected value, and it is easy to adopt because the only change to a test class is adding a trait.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The first run records, the second run compares
The whole idea is in the README's first example. A test class uses the `Spatie\Snapshots\MatchesSnapshots` trait and calls a single assertion on the JSON of an object:
use Spatie\Snapshots\MatchesSnapshots;
class OrderTest
{
use MatchesSnapshots;
public function test_it_casts_to_json()
{
$order = new Order(1);
$this->assertMatchesJsonSnapshot($order->toJson());
}
}On the first run there is no stored snapshot, so the runner creates one and reports the test as incomplete rather than passed. The output names the snapshot it wrote, `Snapshot created for OrderTest__test_it_casts_to_json__1`, and ends with `OK, but incomplete, skipped, or risky tests!` and `Tests: 1, Assertions: 0, Incomplete: 1.` Every run after that compares the produced value against the stored one and passes if nothing changed. That ordering matters for a team adopting this: the first run is not a passing test run, it is a recording run, and the snapshot files it writes become part of your review surface.
What a failure actually prints
The value of this approach is only as good as the diff it gives you, and the README shows the shape of that output. When the order id changes from 1 to 2, the test fails and the runner prints a unified style diff between expected and actual, with the JSON body shown in the pretty printed form the snapshot stored:
> ./vendor/bin/phpunit
1) OrderTest::test_it_casts_to_json
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
Failed asserting that '{"id":2}' matches JSON string "{
"id": 1
}
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.Because the stored snapshot is a formatted JSON document rather than a compact string, the diff lines up field by field. A snapshot of a plain string gives you the plain string, and the failure output for that case is a simple expected versus actual pair. The practical consequence is that a reviewer reads a snapshot change in a pull request the same way they read a code change, and has to judge whether the new content is intended. That review step is the entire safety story of snapshot testing, and it only works if the snapshot files are committed and reviewed rather than regenerated silently.
Ten assertion types, and what they capture
The trait adds a set of assertion methods to your test class, and the README lists them all: `assertMatchesSnapshot($actual, $driver, $id)`, `assertMatchesFileHashSnapshot($filePath, $id)`, `assertMatchesFileSnapshot($file, $id)`, `assertMatchesHtmlSnapshot($actual, $id)`, `assertMatchesJsonSnapshot($actual, $id)`, `assertMatchesObjectSnapshot($actual, $id)`, `assertMatchesTextSnapshot($actual, $id)`, `assertMatchesXmlSnapshot($actual, $id)`, `assertMatchesYamlSnapshot($actual, $id)` and `assertMatchesImageSnapshot($actual, $threshold, $includeAa, $id)`. Each takes an optional id, and the general form takes a driver as well, which is how the package lets you pick the storage format. The image assertion is the most involved, with a threshold and a flag for anti-aliasing, and the repository's package.json shows why: it pulls in `pixelmatch` and `pngjs` as Node dependencies for comparing images. Basic snapshots return a plain text or YAML representation of the actual value, so a string snapshot is literally the word `foo` in a text file.
Snapshot ids come from the test name
Snapshot ids are generated based on the test and test case names, which is why the README output reads `Snapshot created for ExampleTest__test_it_matches_a_string__1`. The identifier encodes the class, the method and a counter, so renaming a test method changes the snapshot file name and leaves the old file behind as an orphan. This is a real operational detail: the snapshot directory accumulates files whose names no longer correspond to any test, and a rename in a test class is enough to create one. Passing an explicit id to the assertion is the escape hatch, and the assertion signatures show it as the last parameter on every method, with the general `assertMatchesSnapshot` also accepting a driver. If your test names are unstable, or your snapshots need to be shared between two different test classes, that id argument is what you will be using.
Updating snapshots when a change is intended
When the new output is correct, the test has to be told to accept it. The package ships a wrapper binary that runs PHPUnit with the right environment variable set:
> ./vendor/bin/update-snapshots
OK (1 test, 1 assertion)The wrapper accepts the same arguments as phpunit, so you can target one file:
> ./vendor/bin/update-snapshots tests/OrderTest.phpYou can also set the variable directly, which the README recommends in CI or when you want a Composer script:
> UPDATE_SNAPSHOTS=true ./vendor/bin/phpunitAnd the Composer script pattern it suggests is:
{
"scripts": {
"update-snapshots": "UPDATE_SNAPSHOTS=true vendor/bin/phpunit"
}
}With that in place you can run `composer update-snapshots`, and pass extra arguments through it as `composer update-snapshots -- tests/OrderTest.php`. The important operational point is that updating is a separate, explicit command rather than something a test does for itself, which means a stale snapshot is a failure you see, not a change that quietly lands on main.
Why the old phpunit -d flag was dropped
The README devotes a section to a deprecation that is easy to trip over. Earlier versions documented `phpunit -d --update-snapshots` as the way to enable updates, and that syntax abused PHPUnit's `-d` flag, which is designed to set `php.ini` values. Since PHPUnit 12.5.12, failed `ini_set()` calls produce a visible warning, `Failed to set "--update-snapshots=1"`, so the misuse now surfaces. The wrapper binary and the environment variable replace that approach. The legacy CLI argument still works for backwards compatibility, but it produces the warning on PHPUnit 12.5.12 and later. The practical consequence for a team that has been using the old flag for years is that the day they upgrade PHPUnit, their snapshot update command starts printing a warning, and the fix is to move to `vendor/bin/update-snapshots` or the environment variable.
Where snapshot testing stops being the right tool
Two limits are worth naming before you adopt this for a whole suite. The first is that a snapshot records what the code produced, not what the code should produce. If a bug exists on the first run, the bug is written into the snapshot and the test then passes forever. That is manageable for large, verbose output where a human reviewer will read the diff anyway, and it is dangerous for small values where a reviewer will skim the file and assume the recorded content is correct. The second is that snapshot files are test fixtures that grow without bound and are rarely pruned. Every rename of a test method adds one, and the README documents no cleanup command, so a project accumulates directories of orphaned snapshots. For a small string or a single integer, a plain assertion states the intent directly and reads better than a fixture file, and it fails with a message that needs no interpretation. The README itself frames the package as usable in non-Laravel projects, so the framework is not the constraint, the shape of the expected output is.
Editorial conclusion
The package suits a PHP or Laravel team that has output shaped like JSON, HTML, XML or YAML and no good way to hand write the expected value, and it is easy to adopt because the only change to a test class is adding a trait. It is the wrong tool when the expected value is small and obvious, because a plain assertEquals is clearer, and it is dangerous as a default for anything where a silently wrong output is acceptable, since a snapshot records whatever the code produced, correct or not. Before you adopt it, read the assertion list and pick the narrowest type that fits, and read the diff output the runner prints on failure. The last push was on 2026-06-26, version 5.4.0 was released on 2026-04-29, and the licence is MIT.
Frequently asked questions
How do I install phpunit-snapshot-assertions?
The README shows the package installed as a development dependency with Composer: `composer require --dev spatie/phpunit-snapshot-assertions`. The package is also listed on Packagist.
How do I update snapshots in phpunit-snapshot-assertions?
Run the bundled wrapper binary with `vendor/bin/update-snapshots`, which accepts the same arguments as phpunit, for example `vendor/bin/update-snapshots tests/OrderTest.php`. You can also set `UPDATE_SNAPSHOTS=true` directly before running phpunit, which the README suggests for CI and for a Composer script.
Which assertion methods does the MatchesSnapshots trait add?
The README lists assertions for general snapshots, file hashes, files, HTML, JSON, objects, text, XML, YAML and images, named `assertMatchesSnapshot`, `assertMatchesFileHashSnapshot`, `assertMatchesFileSnapshot`, `assertMatchesHtmlSnapshot`, `assertMatchesJsonSnapshot`, `assertMatchesObjectSnapshot`, `assertMatchesTextSnapshot`, `assertMatchesXmlSnapshot`, `assertMatchesYamlSnapshot` and `assertMatchesImageSnapshot`.
Is the old `phpunit -d --update-snapshots` syntax still supported?
It still works for backwards compatibility, but it produces a `Failed to set "--update-snapshots=1"` warning on PHPUnit 12.5.12 and later, because that syntax abused PHPUnit's `-d` flag, which is for `php.ini` values. The README recommends the wrapper binary or the `UPDATE_SNAPSHOTS` environment variable instead.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/spatie-phpunit-snapshot-assertions)Was this useful?