Skip to content

Commit b5ad3ef

Browse files
authored
Add update-snapshots wrapper binary (#245)
Add wrapper binaries to replace `-d --update-snapshots` syntax
2 parents ba48973 + 2a5d1e0 commit b5ad3ef

7 files changed

Lines changed: 258 additions & 27 deletions

File tree

README.md

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -169,20 +169,48 @@ FAILURES!
169169
Tests: 1, Assertions: 1, Failures: 1.
170170
```
171171

172-
When we expect a changed value, we need to tell the test runner to update the existing snapshots instead of failing the test. This is possible by adding a`-d --update-snapshots` flag to the `phpunit` command, or setting the `UPDATE_SNAPSHOTS` env var to `true`.
172+
When we expect a changed value, we need to tell the test runner to update the existing snapshots instead of failing the test. The package ships a wrapper binary that runs PHPUnit with the right environment variable set:
173173

174174
```
175-
> ./vendor/bin/phpunit -d --update-snapshots
175+
> ./vendor/bin/update-snapshots
176176
177177
OK (1 test, 1 assertion)
178178
```
179179

180+
`vendor/bin/update-snapshots` accepts the same arguments as `phpunit`, so you can target a specific test:
181+
182+
```
183+
> ./vendor/bin/update-snapshots tests/OrderTest.php
184+
```
185+
180186
As a result, our snapshot file returns "bar" instead of "foo".
181187

182188
```txt
183189
bar
184190
```
185191

192+
You can also set the `UPDATE_SNAPSHOTS` environment variable directly. This is useful in CI, or when you want to define a Composer script for your project.
193+
194+
```
195+
> UPDATE_SNAPSHOTS=true ./vendor/bin/phpunit
196+
```
197+
198+
A common pattern is to add an `update-snapshots` script to your project's `composer.json`:
199+
200+
```json
201+
{
202+
"scripts": {
203+
"update-snapshots": "UPDATE_SNAPSHOTS=true vendor/bin/phpunit"
204+
}
205+
}
206+
```
207+
208+
You can then run `composer update-snapshots` (and pass extra arguments via `composer update-snapshots -- tests/OrderTest.php`).
209+
210+
#### Why not `phpunit -d --update-snapshots`?
211+
212+
Earlier versions of this package documented `phpunit -d --update-snapshots` as the way to enable updates. That syntax abuses PHPUnit's `-d` flag, which is designed to set `php.ini` values. Since [PHPUnit 12.5.12](https://github.com/sebastianbergmann/phpunit/commit/87f68b992979f9e4ad485be959b5e9d0c21597f2), failed `ini_set()` calls produce a visible `Failed to set "--update-snapshots=1"` warning, surfacing the misuse. The wrapper binary and the environment variable replace that approach. The legacy CLI argument still works for backwards compatibility, but produces the warning on PHPUnit 12.5.12 and later.
213+
186214
### File snapshots
187215

188216
The `MatchesSnapshots` trait offers two ways to assert that a file is identical to the snapshot that was made the first time the test was run:
@@ -311,29 +339,39 @@ $this->assertMatchesSnapshot($something->toYaml(), new MyYamlDriver());
311339

312340
### Usage in CI
313341

314-
When running your tests in Continuous Integration you would possibly want to disable the creation of snapshots.
342+
When running your tests in Continuous Integration you would possibly want to disable the creation of snapshots, so missing snapshots cause the build to fail instead of being silently created.
315343

316-
By using the `--without-creating-snapshots` parameter or by setting the `CREATE_SNAPSHOTS` env var to `false`, PHPUnit will fail if the snapshots don't exist.
344+
Set the `CREATE_SNAPSHOTS=false` environment variable when invoking PHPUnit:
317345

318346
```bash
319-
> ./vendor/bin/phpunit -d --without-creating-snapshots
347+
> CREATE_SNAPSHOTS=false ./vendor/bin/phpunit
320348

321349
1) ExampleTest::test_it_matches_a_string
322350
Snapshot "ExampleTest__test_it_matches_a_string__1.txt" does not exist.
323-
You can automatically create it by removing the `CREATE_SNAPSHOTS=false` env var, or `-d --no-create-snapshots` of PHPUnit's CLI arguments.
351+
You can enable snapshot creation by running `vendor/bin/phpunit` directly, or by unsetting the `CREATE_SNAPSHOTS=false` environment variable.
324352
```
325353

354+
```json
355+
{
356+
"scripts": {
357+
"test:ci": "CREATE_SNAPSHOTS=false vendor/bin/phpunit"
358+
}
359+
}
360+
```
361+
362+
> Earlier versions of this package documented `phpunit -d --without-creating-snapshots`. Since [PHPUnit 12.5.12](https://github.com/sebastianbergmann/phpunit/commit/87f68b992979f9e4ad485be959b5e9d0c21597f2), that syntax produces a `Failed to set "--without-creating-snapshots=1"` test runner warning because it abuses PHPUnit's `-d` flag (which is designed for `php.ini` values). The `CREATE_SNAPSHOTS=false` environment variable replaces that approach.
363+
326364
### Usage with parallel testing
327365

328-
If you want to run your test in parallel with a tool like [Paratest](https://github.com/paratestphp/paratest), ou with the `php artisan test --parallel` command of Laravel, you will have to use the environment variables.
366+
If you want to run your test in parallel with a tool like [Paratest](https://github.com/paratestphp/paratest), or with the `php artisan test --parallel` command of Laravel, you will have to use the environment variables.
329367

330368

331369
```bash
332370
> CREATE_SNAPSHOTS=false php artisan test --parallel
333371

334372
1) ExampleTest::test_it_matches_a_string
335373
Snapshot "ExampleTest__test_it_matches_a_string__1.txt" does not exist.
336-
You can automatically create it by removing the `CREATE_SNAPSHOTS=false` env var, or `-d --no-create-snapshots` of PHPUnit's CLI arguments.
374+
You can enable snapshot creation by running `vendor/bin/phpunit` directly, or by unsetting the `CREATE_SNAPSHOTS=false` environment variable.
337375
```
338376

339377
### A note for Windows users

bin/_run-phpunit.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/*
4+
* Locates the PHPUnit binary and runs it. Shared helper used by the
5+
* `update-snapshots` and `without-creating-snapshots` wrappers, which set
6+
* the relevant environment variable before requiring this file.
7+
*/
8+
9+
$candidates = [];
10+
11+
if (isset($GLOBALS['_composer_autoload_path'])) {
12+
$candidates[] = dirname($GLOBALS['_composer_autoload_path']).'/phpunit/phpunit/phpunit';
13+
}
14+
15+
$candidates[] = dirname(__DIR__, 3).'/phpunit/phpunit/phpunit';
16+
$candidates[] = dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
17+
18+
foreach ($candidates as $phpunit) {
19+
if (file_exists($phpunit)) {
20+
require $phpunit;
21+
22+
return;
23+
}
24+
}
25+
26+
fwrite(STDERR, 'Could not locate PHPUnit. Make sure phpunit/phpunit is installed.'.PHP_EOL);
27+
exit(1);

bin/update-snapshots

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env php
2+
<?php
3+
4+
/*
5+
* This wrapper sets the UPDATE_SNAPSHOTS environment variable and then runs
6+
* PHPUnit, so snapshots are updated for this run only.
7+
*
8+
* It exists because the previously documented syntax (`phpunit -d --update-snapshots`)
9+
* abuses PHPUnit's `-d` flag, which is meant to set php.ini values. Since PHPUnit
10+
* 12.5.12 (https://github.com/sebastianbergmann/phpunit/commit/87f68b9), failed
11+
* ini_set() calls produce a visible test runner warning, surfacing that misuse.
12+
*/
13+
14+
// Capture whatever the env var was before this script ran, so we can restore it
15+
// on shutdown. Without this, code running after PHPUnit (shutdown handlers, parent
16+
// processes that include this file) would observe a state mutation they didn't ask
17+
// for. Env values are always strings, so `null` unambiguously means "wasn't set".
18+
$originalGetenv = getenv('UPDATE_SNAPSHOTS');
19+
$originalEnv = $_ENV['UPDATE_SNAPSHOTS'] ?? null;
20+
$originalServer = $_SERVER['UPDATE_SNAPSHOTS'] ?? null;
21+
22+
register_shutdown_function(static function () use ($originalGetenv, $originalEnv, $originalServer): void {
23+
putenv($originalGetenv === false ? 'UPDATE_SNAPSHOTS' : 'UPDATE_SNAPSHOTS='.$originalGetenv);
24+
25+
unset($_ENV['UPDATE_SNAPSHOTS'], $_SERVER['UPDATE_SNAPSHOTS']);
26+
27+
if ($originalEnv !== null) {
28+
$_ENV['UPDATE_SNAPSHOTS'] = $originalEnv;
29+
}
30+
31+
if ($originalServer !== null) {
32+
$_SERVER['UPDATE_SNAPSHOTS'] = $originalServer;
33+
}
34+
});
35+
36+
// Set in all three locations so getenv(), $_ENV[] and $_SERVER[] reads all see the
37+
// value, regardless of the user's `variables_order` ini setting.
38+
putenv('UPDATE_SNAPSHOTS=true');
39+
$_ENV['UPDATE_SNAPSHOTS'] = 'true';
40+
$_SERVER['UPDATE_SNAPSHOTS'] = 'true';
41+
42+
require __DIR__.'/_run-phpunit.php';

composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@
4747
"Spatie\\Snapshots\\Test\\": "tests"
4848
}
4949
},
50+
"bin": [
51+
"bin/update-snapshots"
52+
],
5053
"scripts": {
5154
"test": "phpunit"
5255
},

src/MatchesSnapshots.php

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -137,29 +137,32 @@ protected function getFileSnapshotDirectory(): string
137137
* Determines whether or not the snapshot should be updated instead of
138138
* matched.
139139
*
140-
* Override this method it you want to use a different flag or mechanism
141-
* than `-d --update-snapshots` or `UPDATE_SNAPSHOTS=true` env var.
140+
* Override this method if you want to use a different flag or mechanism
141+
* than `vendor/bin/update-snapshots` or the `UPDATE_SNAPSHOTS=true` env var.
142142
*/
143143
protected function shouldUpdateSnapshots(): bool
144144
{
145-
if (in_array('--update-snapshots', $_SERVER['argv'], true)) {
145+
if (getenv('UPDATE_SNAPSHOTS') === 'true') {
146146
return true;
147147
}
148148

149-
return getenv('UPDATE_SNAPSHOTS') === 'true';
149+
return in_array('--update-snapshots', $_SERVER['argv'], true);
150150
}
151151

152152
/*
153153
* Determines whether or not the snapshot should be created instead of
154154
* matched.
155155
*
156156
* Override this method if you want to use a different flag or mechanism
157-
* than `-d --without-creating-snapshots` or `CREATE_SNAPSHOTS=false` env var.
157+
* than the `CREATE_SNAPSHOTS=false` env var.
158158
*/
159159
protected function shouldCreateSnapshots(): bool
160160
{
161-
return ! in_array('--without-creating-snapshots', $_SERVER['argv'], true)
162-
&& getenv('CREATE_SNAPSHOTS') !== 'false';
161+
if (getenv('CREATE_SNAPSHOTS') === 'false') {
162+
return false;
163+
}
164+
165+
return ! in_array('--without-creating-snapshots', $_SERVER['argv'], true);
163166
}
164167

165168
protected function resolveSnapshotId(?string $id = null): string
@@ -296,8 +299,8 @@ protected function updateSnapshotAndMarkTestIncomplete(Snapshot $snapshot, $actu
296299
protected function rethrowExpectationFailedExceptionWithUpdateSnapshotsPrompt($exception): void
297300
{
298301
$newMessage = $exception->getMessage()."\n\n".
299-
'Snapshots can be updated by passing '.
300-
'`-d --update-snapshots` through PHPUnit\'s CLI arguments.';
302+
'Snapshots can be updated by running `vendor/bin/update-snapshots`, '.
303+
'or by setting the `UPDATE_SNAPSHOTS=true` environment variable.';
301304

302305
$exceptionReflection = new ReflectionObject($exception);
303306

@@ -320,9 +323,8 @@ protected function assertSnapshotShouldBeCreated(string $snapshotFileName): void
320323

321324
$this->fail(
322325
"Snapshot \"$snapshotFileName\" does not exist.\n".
323-
'You can automatically create it by removing '.
324-
'the `CREATE_SNAPSHOTS=false` env var, or '.
325-
'`-d --without-creating-snapshots` of PHPUnit\'s CLI arguments.'
326+
'You can enable snapshot creation by running `vendor/bin/phpunit` directly, '.
327+
'or by unsetting the `CREATE_SNAPSHOTS=false` environment variable.'
326328
);
327329
}
328330
}

tests/Integration/MatchesSnapshotTest.php

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace Spatie\Snapshots\Test\Integration;
44

5-
use PHPUnit\Framework\AssertionFailedError;
65
use PHPUnit\Framework\Attributes\Test;
76
use PHPUnit\Framework\ExpectationFailedException;
87
use PHPUnit\Framework\MockObject\MockObject;
@@ -478,13 +477,17 @@ private function expectIncompleteMatchesSnapshotTest(MockObject $matchesSnapshot
478477

479478
private function expectFail(MockObject $matchesSnapshotMock, string $message)
480479
{
481-
$this->expectException(AssertionFailedError::class);
480+
// We throw a plain RuntimeException from the mock instead of
481+
// AssertionFailedError because PHPUnit 12.5+ tracks AssertionFailedError
482+
// thrown from a mock as a test failure even when user code catches it.
483+
$this->expectException(\RuntimeException::class);
484+
$this->expectExceptionMessage($message);
482485

483486
$matchesSnapshotMock
484487
->expects($this->once())
485488
->method('fail')
486489
->with($message)
487-
->willThrowException(new AssertionFailedError);
490+
->willThrowException(new \RuntimeException($message));
488491
}
489492

490493
private function expectFailedMatchesSnapshotTest()
@@ -563,7 +566,7 @@ public function it_doesnt_create_a_regular_snapshot_and_mismatches_if_asked()
563566
$this->expectFail(
564567
$mockTrait,
565568
"Snapshot \"MatchesSnapshotTest__it_doesnt_create_a_regular_snapshot_and_mismatches_if_asked__1.txt\" does not exist.\n".
566-
"You can automatically create it by removing the `CREATE_SNAPSHOTS=false` env var, or `-d --without-creating-snapshots` of PHPUnit's CLI arguments."
569+
'You can enable snapshot creation by running `vendor/bin/phpunit` directly, or by unsetting the `CREATE_SNAPSHOTS=false` environment variable.'
567570
);
568571

569572
$mockTrait->assertMatchesSnapshot('Bar');
@@ -580,7 +583,7 @@ public function it_doesnt_create_a_file_snapshot_and_mismatches_if_asked()
580583
$this->expectFail(
581584
$mockTrait,
582585
"Snapshot \"MatchesSnapshotTest__it_doesnt_create_a_file_snapshot_and_mismatches_if_asked__1.jpg_failed.jpg\" does not exist.\n".
583-
"You can automatically create it by removing the `CREATE_SNAPSHOTS=false` env var, or `-d --without-creating-snapshots` of PHPUnit's CLI arguments."
586+
'You can enable snapshot creation by running `vendor/bin/phpunit` directly, or by unsetting the `CREATE_SNAPSHOTS=false` environment variable.'
584587
);
585588

586589
$mockTrait->assertMatchesFileSnapshot(__DIR__.'/stubs/test_files/friendly_man.jpg');
@@ -597,7 +600,7 @@ public function it_doesnt_create_a_regular_snapshot_and_mismatches_if_asked_with
597600
$this->expectFail(
598601
$mockTrait,
599602
"Snapshot \"MatchesSnapshotTest__it_doesnt_create_a_regular_snapshot_and_mismatches_if_asked_with_env_var__1.txt\" does not exist.\n".
600-
"You can automatically create it by removing the `CREATE_SNAPSHOTS=false` env var, or `-d --without-creating-snapshots` of PHPUnit's CLI arguments."
603+
'You can enable snapshot creation by running `vendor/bin/phpunit` directly, or by unsetting the `CREATE_SNAPSHOTS=false` environment variable.'
601604
);
602605

603606
$mockTrait->assertMatchesSnapshot('Bar');
@@ -614,7 +617,7 @@ public function it_doesnt_create_a_file_snapshot_and_mismatches_if_asked_with_en
614617
$this->expectFail(
615618
$mockTrait,
616619
"Snapshot \"MatchesSnapshotTest__it_doesnt_create_a_file_snapshot_and_mismatches_if_asked_with_env_var__1.jpg_failed.jpg\" does not exist.\n".
617-
"You can automatically create it by removing the `CREATE_SNAPSHOTS=false` env var, or `-d --without-creating-snapshots` of PHPUnit's CLI arguments."
620+
'You can enable snapshot creation by running `vendor/bin/phpunit` directly, or by unsetting the `CREATE_SNAPSHOTS=false` environment variable.'
618621
);
619622

620623
$mockTrait->assertMatchesFileSnapshot(__DIR__.'/stubs/test_files/friendly_man.jpg');

0 commit comments

Comments
 (0)