Skip to content

Commit c293e8d

Browse files
committed
add Assert::snapshot() and make Snapshot an instantiable representation of a single snapshot
1 parent 1f9d613 commit c293e8d

10 files changed

+159
-106
lines changed

src/Framework/Assert.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,40 @@ public static function matchFile(string $file, $actual, string $description = nu
416416
}
417417

418418

419+
/**
420+
* Compares value with a previously created snapshot.
421+
*/
422+
public static function snapshot(string $snapshotName, $actual, string $description = null): void
423+
{
424+
self::$counter++;
425+
426+
$snapshot = new Snapshot($snapshotName);
427+
if (!$snapshot->exists()) {
428+
if (!$snapshot->canUpdate()) {
429+
self::fail("Missing snapshot '$snapshotName', use --update-snapshots option to generate it.");
430+
}
431+
432+
$snapshot->update($actual);
433+
}
434+
435+
$expected = $snapshot->read();
436+
if ($expected !== $actual) {
437+
if (!$snapshot->canUpdate()) {
438+
self::fail(
439+
self::describe(
440+
"%1 should be %2 in snapshot '$snapshotName'",
441+
$description
442+
),
443+
$actual,
444+
$expected
445+
);
446+
}
447+
448+
$snapshot->update($actual);
449+
}
450+
}
451+
452+
419453
/**
420454
* Failed assertion
421455
*/

src/Framework/Snapshot.php

Lines changed: 48 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -11,71 +11,70 @@
1111

1212

1313
/**
14-
* Snapshot testing helper.
14+
* Snapshot of a tested value.
1515
*/
1616
class Snapshot
1717
{
18+
/** @var string */
1819
public static $snapshotDir = 'snapshots';
1920

21+
/** @var string[] */
2022
public static $updatedSnapshots = [];
2123

24+
/** @var string[] */
25+
private static $usedNames = [];
2226

23-
/**
24-
* Compares value with a previously created snapshot.
25-
*/
26-
public static function match($value, string $snapshotName): void
27-
{
28-
$updateSnapshots = (bool) getenv(Environment::UPDATE_SNAPSHOTS);
29-
30-
$testFile = $_SERVER['argv'][0];
31-
$snapshotFile = self::getSnapshotFile($testFile, $snapshotName);
27+
/** @var string */
28+
private $name;
3229

33-
if (!file_exists($snapshotFile)) {
34-
if (!$updateSnapshots) {
35-
Assert::fail("Missing snapshot file '$snapshotFile', use --update-snapshots option to generate it.");
36-
}
3730

38-
self::write($snapshotFile, $value);
31+
public function __construct(string $name)
32+
{
33+
if (!preg_match('/^[a-zA-Z0-9-_]+$/', $name)) {
34+
throw new \Exception("Invalid snapshot name '$name'. Only alphanumeric characters, dash and underscore are allowed.");
3935
}
4036

41-
$snapshot = self::read($snapshotFile);
37+
if (in_array($name, self::$usedNames, true)) {
38+
throw new \Exception("Snapshot '$name' was already asserted, please use a different name.");
39+
}
4240

43-
try {
44-
Assert::equal($snapshot, $value, "Snapshot $snapshotName");
41+
$this->name = self::$usedNames[] = $name;
42+
}
4543

46-
} catch (AssertException $e) {
47-
if (!$updateSnapshots) {
48-
throw $e;
49-
}
5044

51-
self::write($snapshotFile, $value);
52-
}
45+
public function exists(): bool
46+
{
47+
return file_exists($this->getSnapshotFile());
5348
}
5449

5550

56-
private static function getSnapshotFile(string $testFile, string $snapshotName): string
51+
public function read()
5752
{
58-
$path = self::$snapshotDir . DIRECTORY_SEPARATOR . pathinfo($testFile, PATHINFO_FILENAME) . '.' . $snapshotName . '.phps';
59-
if (!preg_match('#/|\w:#A', self::$snapshotDir)) {
60-
$path = dirname($testFile) . DIRECTORY_SEPARATOR . $path;
61-
}
62-
return $path;
53+
$snapshotFile = $this->getSnapshotFile();
54+
set_error_handler(function ($errno, $errstr) use ($snapshotFile) {
55+
throw new \Exception("Unable to read snapshot file '$snapshotFile': $errstr");
56+
});
57+
58+
$snapshotContents = include $snapshotFile;
59+
60+
restore_error_handler();
61+
return $snapshotContents;
6362
}
6463

6564

66-
private static function read(string $snapshotFile)
65+
public function canUpdate(): bool
6766
{
68-
$snapshotContents = @file_get_contents($snapshotFile);
69-
if ($snapshotContents === false) {
70-
throw new \Exception("Unable to read snapshot file '$snapshotFile'.");
71-
}
72-
73-
return eval(substr($snapshotContents, strlen('<?php ')));
67+
return (bool) getenv(Environment::UPDATE_SNAPSHOTS);
7468
}
7569

7670

77-
private static function write(string $snapshotFile, $value): void
71+
public function update($value): void
7872
{
73+
if (!$this->canUpdate()) {
74+
throw new \Exception("Cannot update snapshot. Please run tests again with --update-snapshots.");
75+
}
76+
77+
$snapshotFile = $this->getSnapshotFile();
7978
$snapshotDirectory = dirname($snapshotFile);
8079
if (!is_dir($snapshotDirectory) && !mkdir($snapshotDirectory)) {
8180
throw new \Exception("Unable to create snapshot directory '$snapshotDirectory'.");
@@ -88,4 +87,15 @@ private static function write(string $snapshotFile, $value): void
8887

8988
self::$updatedSnapshots[] = $snapshotFile;
9089
}
90+
91+
92+
private function getSnapshotFile(): string
93+
{
94+
$testFile = $_SERVER['argv'][0];
95+
$path = self::$snapshotDir . DIRECTORY_SEPARATOR . pathinfo($testFile, PATHINFO_FILENAME) . '.' . $this->name . '.phps';
96+
if (!preg_match('#/|\w:#A', self::$snapshotDir)) {
97+
$path = dirname($testFile) . DIRECTORY_SEPARATOR . $path;
98+
}
99+
return $path;
100+
}
91101
}

tests/Framework/Assert.snapshot.phpt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Tester\Assert;
6+
use Tester\AssertException;
7+
use Tester\Snapshot;
8+
9+
require __DIR__ . '/../bootstrap.php';
10+
11+
Snapshot::$snapshotDir = __DIR__ . '/fixtures';
12+
13+
Assert::snapshot('existingSnapshot', ['answer' => 42]);
14+
15+
Assert::exception(function () {
16+
Assert::snapshot('invalid / name', ['answer' => 42]);
17+
}, \Exception::class, "Invalid snapshot name 'invalid / name'. Only alphanumeric characters, dash and underscore are allowed.");
18+
19+
Assert::exception(function () {
20+
Assert::snapshot('existingSnapshot', ['answer' => 42]);
21+
}, \Exception::class, "Snapshot 'existingSnapshot' was already asserted, please use a different name.");
22+
23+
Assert::exception(function () {
24+
Assert::snapshot('anotherSnapshot', ['answer' => 43]);
25+
}, AssertException::class, "%a% should be %a% in snapshot 'anotherSnapshot'");
26+
27+
Assert::exception(function () {
28+
Assert::snapshot('nonExistingSnapshot', 'value');
29+
}, AssertException::class, "Missing snapshot 'nonExistingSnapshot', use --update-snapshots option to generate it.");
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Tester\Assert;
6+
use Tester\Environment;
7+
use Tester\Helpers;
8+
use Tester\Snapshot;
9+
10+
require __DIR__ . '/../bootstrap.php';
11+
12+
putenv(Environment::UPDATE_SNAPSHOTS . '=1');
13+
Snapshot::$snapshotDir = __DIR__ . '/snapshots';
14+
Helpers::purge(Snapshot::$snapshotDir);
15+
16+
// newly created
17+
18+
Assert::false(file_exists(Snapshot::$snapshotDir . '/Assert.snapshot.update.newSnapshot.phps'));
19+
Assert::snapshot('newSnapshot', ['answer' => 42]);
20+
Assert::true(file_exists(Snapshot::$snapshotDir . '/Assert.snapshot.update.newSnapshot.phps'));
21+
Assert::contains('42', file_get_contents(Snapshot::$snapshotDir . '/Assert.snapshot.update.newSnapshot.phps'));
22+
23+
// existing
24+
25+
file_put_contents(
26+
Snapshot::$snapshotDir . '/Assert.snapshot.update.updatedSnapshot.phps',
27+
'<?php return array(\'answer\' => 43);' . PHP_EOL
28+
);
29+
30+
Assert::true(file_exists(Snapshot::$snapshotDir . '/Assert.snapshot.update.updatedSnapshot.phps'));
31+
Assert::snapshot('updatedSnapshot', ['answer' => 42]);
32+
Assert::true(file_exists(Snapshot::$snapshotDir . '/Assert.snapshot.update.updatedSnapshot.phps'));
33+
Assert::contains('42', file_get_contents(Snapshot::$snapshotDir . '/Assert.snapshot.update.updatedSnapshot.phps'));
34+
35+
// Snapshot::$updatedSnapshots
36+
37+
Assert::same([
38+
Snapshot::$snapshotDir . DIRECTORY_SEPARATOR . 'Assert.snapshot.update.newSnapshot.phps',
39+
Snapshot::$snapshotDir . DIRECTORY_SEPARATOR . 'Assert.snapshot.update.updatedSnapshot.phps',
40+
], Snapshot::$updatedSnapshots);
41+
42+
// reset the env variable so that the test does not fail due to updated snapshots
43+
putenv(Environment::UPDATE_SNAPSHOTS . '=0');

tests/Framework/Snapshot.match.phpt

Lines changed: 0 additions & 21 deletions
This file was deleted.

tests/Framework/Snapshot.update.phpt

Lines changed: 0 additions & 43 deletions
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<?php return ['answer' => 42];

tests/Runner/Runner.snapshots.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ $runner->run();
5252

5353
Assert::same(Test::FAILED, $logger->results['update-snapshots.phptx'][0]);
5454
Assert::match(
55-
"Failed: Missing snapshot file '%a%', use --update-snapshots option to generate it.\n%A%",
55+
"Failed: Missing snapshot '%a%', use --update-snapshots option to generate it.\n%A%",
5656
trim(Dumper::removeColors($logger->results['update-snapshots.phptx'][1]))
5757
);
5858

59-
// second run, with update -> skipped
59+
// second run, with update -> fail
6060

6161
$runner = new Tester\Runner\Runner(createInterpreter());
6262
$runner->paths[] = __DIR__ . '/snapshots/*.phptx';
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22

3-
use Tester\Snapshot;
3+
use Tester\Assert;
44

55
require __DIR__ . '/../../bootstrap.php';
66

7-
Snapshot::match('snapshot value', 'snapshot');
7+
Assert::snapshot('snapshot', 'snapshot value');

0 commit comments

Comments
 (0)