Skip to content

Commit 7147881

Browse files
committed
add file snapshot assertion
1 parent e551d16 commit 7147881

14 files changed

+206
-1
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,12 @@ composer require spatie/phpunit-snapshot-assertions
7878

7979
## Usage
8080

81-
To make snapshot assertions, use the `Spatie\Snapshots\MatchesSnapshots` trait in your test case class. This adds four assertion methods to the class:
81+
To make snapshot assertions, use the `Spatie\Snapshots\MatchesSnapshots` trait in your test case class. This adds five assertion methods to the class:
8282

8383
- `assertMatchesSnapshot($actual)`
8484
- `assertMatchesJsonSnapshot($actual)`
8585
- `assertMatchesXmlSnapshot($actual)`
86+
- `assertMatchesFileSnapshot($filePath)`
8687
- `assertMatchesFileHashSnapshot($filePath)`
8788

8889
### Snapshot Testing 101
@@ -159,6 +160,13 @@ As a result, our snapshot file returns "bar" instead of "foo".
159160
<?php return 'bar';
160161
```
161162

163+
### File snapshots
164+
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:
165+
166+
The `assertMatchesFileHashSnapshot($filePath)` assertion asserts that the hash of the file passed into the function and the hash saved in the snapshot match. This assertion is fast and uses very little disk space. The downside of this assertion is that there is no easy way to see how the two files differ if the test fails.
167+
168+
The `assertMatchesFileSnapshot($filePath)` assertion works almost the same way as the file hash assertion, except that it actually saves the whole file in the snapshots directory. If the assertion fails, it places the failed file next to the snapshot file so they can easily be manually compared. The persisted failed file is automatically deleted when the test passes. This assertion is most useful when working with binary files that should be manually compared like images or pdfs.
169+
162170
### Customizing Snapshot Ids and Directories
163171

164172
Snapshot ids are generated via the `getSnapshotId` method on the `MatchesSnapshot` trait. Override the method to customize the id. By default, a snapshot id exists of the test name, the test case name and an incrementing value, e.g. `Test__my_test_case__1`.

src/Filesystem.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,23 @@ public function put(string $filename, string $contents)
4040

4141
file_put_contents($this->path($filename), $contents);
4242
}
43+
44+
public function delete(string $fileName)
45+
{
46+
return unlink($this->path($fileName));
47+
}
48+
49+
public function copy(string $filePath, string $fileName)
50+
{
51+
if (! file_exists($this->basePath)) {
52+
mkdir($this->basePath);
53+
}
54+
55+
copy($filePath, $this->path($fileName));
56+
}
57+
58+
public function fileEquals(string $filePath, string $fileName)
59+
{
60+
return sha1_file($filePath) === sha1_file($this->path($fileName));
61+
}
4362
}

src/MatchesSnapshots.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ public function assertMatchesFileHashSnapshot($filePath)
4747
$this->assertMatchesSnapshot($actual);
4848
}
4949

50+
public function assertMatchesFileSnapshot($file)
51+
{
52+
$this->doFileSnapshotAssertion($file);
53+
}
54+
5055
/**
5156
* Determines the snapshot's id. By default, the test case's class and
5257
* method names are used.
@@ -74,6 +79,20 @@ protected function getSnapshotDirectory(): string
7479
'__snapshots__';
7580
}
7681

82+
/**
83+
* Determines the directory where file snapshots are stored. By default a
84+
* `__snapshots__/files` directory is created at the same level as the
85+
* test class.
86+
*
87+
* @return string
88+
*/
89+
protected function getFileSnapshotDirectory(): string
90+
{
91+
return $this->getSnapshotDirectory().
92+
DIRECTORY_SEPARATOR.
93+
'files';
94+
}
95+
7796
/**
7897
* Determines whether or not the snapshot should be updated instead of
7998
* matched.
@@ -124,6 +143,51 @@ protected function doSnapshotAssertion($actual, Driver $driver)
124143
}
125144
}
126145

146+
protected function doFileSnapshotAssertion(string $filePath)
147+
{
148+
if (! file_exists($filePath)) {
149+
$this->fail('File does not exist');
150+
}
151+
152+
$fileExtension = pathinfo($filePath, PATHINFO_EXTENSION);
153+
154+
if (empty($fileExtension)) {
155+
$this->fail("Unable to make a file snapshot, file does not have a file extension ({$filePath})");
156+
}
157+
158+
$fileSystem = Filesystem::inDirectory($this->getFileSnapshotDirectory());
159+
160+
$this->snapshotIncrementor++;
161+
162+
$snapshotId = $this->getSnapshotId().'.'.$fileExtension;
163+
164+
$failedSnapshotId = $snapshotId.'_failed.'.$fileExtension;
165+
166+
if ($fileSystem->has($failedSnapshotId)) {
167+
$fileSystem->delete($failedSnapshotId);
168+
}
169+
170+
if (! $fileSystem->has($snapshotId)) {
171+
$fileSystem->copy($filePath, $snapshotId);
172+
173+
$this->markTestIncomplete("File snapshot created for {$snapshotId}");
174+
}
175+
176+
if (! $fileSystem->fileEquals($filePath, $snapshotId)) {
177+
if ($this->shouldUpdateSnapshots()) {
178+
$fileSystem->copy($filePath, $snapshotId);
179+
180+
$this->markTestIncomplete("File snapshot updated for {$snapshotId}");
181+
}
182+
183+
$fileSystem->copy($filePath, $failedSnapshotId);
184+
185+
$this->fail("File did not match snapshot ({$snapshotId})");
186+
}
187+
188+
$this->assertTrue(true);
189+
}
190+
127191
protected function createSnapshotAndMarkTestIncomplete(Snapshot $snapshot, $actual)
128192
{
129193
$snapshot->create($actual);

tests/Integration/AssertionTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ public function can_match_a_file_hash_snapshot()
4949
$this->assertMatchesFileHashSnapshot($filePath);
5050
}
5151

52+
/** @test */
53+
public function can_match_a_file_snapshot()
54+
{
55+
$filePath = __DIR__.'/stubs/test_files/friendly_man.jpg';
56+
57+
$this->assertMatchesFileSnapshot($filePath);
58+
}
59+
5260
/** @test */
5361
public function can_do_multiple_snapshot_assertions()
5462
{

tests/Integration/MatchesSnapshotTest.php

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,21 @@ public function it_can_create_a_snapshot_from_json()
6868
);
6969
}
7070

71+
/** @test */
72+
public function it_can_create_a_snapshot_from_a_file()
73+
{
74+
$mockTrait = $this->getMatchesSnapshotMock();
75+
76+
$this->expectIncompleteMatchesSnapshotTest($mockTrait);
77+
78+
$mockTrait->assertMatchesFileSnapshot(__DIR__.'/stubs/test_files/friendly_man.jpg');
79+
80+
$this->assertSnapshotMatchesExample(
81+
'files/MatchesSnapshotTest__it_can_create_a_snapshot_from_a_file__1.jpg',
82+
'file.jpg'
83+
);
84+
}
85+
7186
/** @test */
7287
public function it_can_match_an_existing_string_snapshot()
7388
{
@@ -140,6 +155,65 @@ public function it_can_mismatch_a_file_hash_snapshot()
140155
$mockTrait->assertMatchesFileHashSnapshot(__DIR__.'/stubs/example_snapshots/snapshot.json');
141156
}
142157

158+
/** @test */
159+
public function it_can_mismatch_a_file_snapshot()
160+
{
161+
$mockTrait = $this->getMatchesSnapshotMock();
162+
163+
$this->expectFail($mockTrait);
164+
165+
$mockTrait->assertMatchesFileSnapshot(__DIR__.'/stubs/test_files/troubled_man.jpg');
166+
}
167+
168+
/** @test */
169+
public function it_needs_a_file_extension_to_do_a_file_snapshot_assertion()
170+
{
171+
$mockTrait = $this->getMatchesSnapshotMock();
172+
173+
$this->expectFail($mockTrait);
174+
175+
$filePath = __DIR__.'/stubs/test_files/file_without_extension';
176+
177+
$this->assertFileExists($filePath);
178+
179+
$mockTrait->assertMatchesFileSnapshot($filePath);
180+
}
181+
182+
/** @test */
183+
public function it_persists_the_failed_file_after_mismatching_a_file_snapshot()
184+
{
185+
$mockTrait = $this->getMatchesSnapshotMock();
186+
187+
$this->expectFail($mockTrait);
188+
189+
$mismatchedFile = __DIR__.'/stubs/test_files/troubled_man.jpg';
190+
191+
$mockTrait->assertMatchesFileSnapshot($mismatchedFile);
192+
193+
$persistedFailedFile = __DIR__.'/__snapshots__/files/MatchesSnapshotTest__it_persists_the_failed_file_after_mismatching_a_file_snapshot__1.jpg_failed.jpg';
194+
195+
$this->assertFileExists($persistedFailedFile);
196+
$this->assertFileEquals($mismatchedFile, $persistedFailedFile);
197+
}
198+
199+
/** @test */
200+
public function it_deletes_the_persisted_failed_file_before_a_file_snapshot_assertion()
201+
{
202+
$mockTrait = $this->getMatchesSnapshotMock();
203+
204+
$mockTrait
205+
->expects($this->once())
206+
->method('assertTrue');
207+
208+
$persistedFailedFile = __DIR__.'/__snapshots__/files/MatchesSnapshotTest__it_deletes_the_persisted_failed_file_before_a_file_snapshot_assertion__1.jpg_failed.jpg';
209+
210+
$this->assertTrue(touch($persistedFailedFile));
211+
212+
$mockTrait->assertMatchesFileSnapshot(__DIR__.'/stubs/test_files/friendly_man.jpg');
213+
214+
$this->assertFileNotExists($persistedFailedFile);
215+
}
216+
143217
/** @test */
144218
public function it_can_update_a_string_snapshot()
145219
{
@@ -191,13 +265,37 @@ public function it_can_update_a_json_snapshot()
191265
);
192266
}
193267

268+
/** @test */
269+
public function it_can_update_a_file_snapshot()
270+
{
271+
$_SERVER['argv'][] = '--update-snapshots';
272+
273+
$mockTrait = $this->getMatchesSnapshotMock();
274+
275+
$this->expectIncompleteMatchesSnapshotTest($mockTrait);
276+
277+
$mockTrait->assertMatchesFileSnapshot(__DIR__.'/stubs/test_files/friendly_man.jpg');
278+
279+
$this->assertSnapshotMatchesExample(
280+
'files/MatchesSnapshotTest__it_can_update_a_file_snapshot__1.jpg',
281+
'file.jpg'
282+
);
283+
}
284+
194285
private function expectIncompleteMatchesSnapshotTest(PHPUnit_Framework_MockObject_MockObject $matchesSnapshotMock)
195286
{
196287
$matchesSnapshotMock
197288
->expects($this->once())
198289
->method('markTestIncomplete');
199290
}
200291

292+
private function expectFail(PHPUnit_Framework_MockObject_MockObject $matchesSnapshotMock)
293+
{
294+
$matchesSnapshotMock
295+
->expects($this->once())
296+
->method('fail');
297+
}
298+
201299
private function expectFailedMatchesSnapshotTest()
202300
{
203301
if (class_exists('PHPUnit\Framework\ExpectationFailedException')) {
@@ -216,6 +314,9 @@ private function getMatchesSnapshotMock(): PHPUnit_Framework_MockObject_MockObje
216314
'markTestIncomplete',
217315
'getSnapshotId',
218316
'getSnapshotDirectory',
317+
'getFileSnapshotDirectory',
318+
'fail',
319+
'assertTrue',
219320
];
220321

221322
$matchesSnapshotMock = $this->getMockForTrait(
@@ -234,6 +335,11 @@ private function getMatchesSnapshotMock(): PHPUnit_Framework_MockObject_MockObje
234335
->method('getSnapshotDirectory')
235336
->willReturn(__DIR__.'/__snapshots__');
236337

338+
$matchesSnapshotMock
339+
->expects($this->any())
340+
->method('getFileSnapshotDirectory')
341+
->willReturn(__DIR__.'/__snapshots__/files');
342+
237343
return $matchesSnapshotMock;
238344
}
239345
}
7.69 KB
Loading
7.69 KB
Loading
3.09 KB
Loading
Loading
Loading

0 commit comments

Comments
 (0)