Skip to content

Commit

Permalink
feat: Driver with JSONPath support
Browse files Browse the repository at this point in the history
  • Loading branch information
chr-hertel committed Apr 5, 2024
1 parent 4f37808 commit 7057c51
Show file tree
Hide file tree
Showing 8 changed files with 286 additions and 1 deletion.
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@
"symfony/yaml": "^5.2|^6.2|^7.0"
},
"require-dev": {
"galbar/jsonpath": "^3.0",
"spatie/pixelmatch-php": "dev-main",
"spatie/ray": "^1.37"
},
"suggest": {
"spatie/pixelmatch-php": "Required to use the image snapshot assertions"
"spatie/pixelmatch-php": "Required to use the image snapshot assertions",
"galbar/jsonpath": "Required to use the JSONPath assertions"
},
"autoload": {
"psr-4": {
Expand Down
45 changes: 45 additions & 0 deletions src/Drivers/JsonPathDriver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace Spatie\Snapshots\Drivers;

use Exception;
use JsonPath\JsonObject;
use PHPUnit\Framework\Assert;

class JsonPathDriver extends JsonDriver
{
public function __construct(private readonly array $placeholders = [])
{
}

public function match($expected, $actual): void
{
if (! class_exists(JsonObject::class)) {
throw new Exception('The galbar/jsonpath package is not installed. Please install it to enable JSONPath driver.');
}

if (is_string($actual)) {
$actual = json_decode($actual, false, 512, JSON_THROW_ON_ERROR);
}

$expected = json_decode($expected, false, 512, JSON_THROW_ON_ERROR);

$jpActual = new JsonObject($actual);
$jpExpected = new JsonObject($expected);
foreach ($this->placeholders as $path => $pattern) {
$actualData = $jpActual->getJsonObjects($path);

if (0 === count($actualData)) {
Assert::fail('Failed asserting that JSON path "'.$path.'" exists.');
}

$expectedData = $jpExpected->getJsonObjects($path);
foreach ($actualData as $i => $data) {
Assert::assertMatchesRegularExpression($pattern, $data->getValue(), 'Failed asserting that JSON path "'.$path.'" matches pattern "'.$pattern.'".');
$data->set('$', $expectedData[$i]->getValue());
}
}

Assert::assertJsonStringEqualsJsonString($jpExpected->getJson(), $jpActual->getJson());
}
}
6 changes: 6 additions & 0 deletions src/MatchesSnapshots.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Spatie\Snapshots\Drivers\HtmlDriver;
use Spatie\Snapshots\Drivers\ImageDriver;
use Spatie\Snapshots\Drivers\JsonDriver;
use Spatie\Snapshots\Drivers\JsonPathDriver;
use Spatie\Snapshots\Drivers\ObjectDriver;
use Spatie\Snapshots\Drivers\TextDriver;
use Spatie\Snapshots\Drivers\XmlDriver;
Expand Down Expand Up @@ -94,6 +95,11 @@ public function assertMatchesJsonSnapshot(array|string|null|int|float|bool $actu
$this->assertMatchesSnapshot($actual, new JsonDriver());
}

public function assertMatchesJsonPathSnapshot(array|string|null|int|float|bool $actual, array $placeholders): void
{
$this->assertMatchesSnapshot($actual, new JsonPathDriver($placeholders));
}

public function assertMatchesObjectSnapshot($actual): void
{
$this->assertMatchesSnapshot($actual, new ObjectDriver());
Expand Down
60 changes: 60 additions & 0 deletions tests/Unit/Drivers/JsonPathDriverTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace Spatie\Snapshots\Test\Unit\Drivers;

use Generator;
use JsonPath\JsonObject;
use JsonPath\JsonPath;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\ExpectationFailedException;
use PHPUnit\Framework\TestCase;
use Spatie\Snapshots\Drivers\JsonPathDriver;

class JsonPathDriverTest extends TestCase
{
#[Test]
#[DataProvider('provideJsonData')]
public function it_can_replace_placeholders_in_json(string $pathExpected, string $pathActual, array $replacements): void
{
$expected = file_get_contents($pathExpected);
$actual = file_get_contents($pathActual);
$driver = new JsonPathDriver($replacements);

try {
$driver->match($expected, $actual);
$status = true;
} catch (ExpectationFailedException $e) {
print(PHP_EOL.PHP_EOL.$e->getMessage().PHP_EOL.PHP_EOL);
$status = false;
}

$this->assertTrue($status);
}

public static function provideJsonData(): Generator
{
yield 'simple' => [
dirname(__DIR__).'/test_files/json_path_simpleA.json',
dirname(__DIR__).'/test_files/json_path_simpleB.json',
[
'$.id' => '@\d+@',
'$.cover' => '@https://bucket.foo/bar/\d+.[webp|jpg]@',
'$.createdAt' => '@\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[\+\-]\d{2}:\d{2}@',
]
];

yield 'json:api' => [
dirname(__DIR__).'/test_files/json_path_jsonapiA.json',
dirname(__DIR__).'/test_files/json_path_jsonapiB.json',
[
'$.data..id' => '@\d+@',
'$.data..links.*' => '@http://example.com/articles/\d+(/[a-z/]+)?@',
'$.included..id' => '@\d+@',
'$.included..links.self' => '@http://example.com/(people|comments)/\d+@',
]
];
}
}
76 changes: 76 additions & 0 deletions tests/Unit/test_files/json_path_jsonapiA.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{
"links": {
"self": "http://example.com/articles",
"next": "http://example.com/articles?page[offset]=2",
"last": "http://example.com/articles?page[offset]=10"
},
"data": [{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON:API paints my bikeshed!"
},
"relationships": {
"author": {
"links": {
"self": "http://example.com/articles/1/relationships/author",
"related": "http://example.com/articles/1/author"
},
"data": { "type": "people", "id": "9" }
},
"comments": {
"links": {
"self": "http://example.com/articles/1/relationships/comments",
"related": "http://example.com/articles/1/comments"
},
"data": [
{ "type": "comments", "id": "5" },
{ "type": "comments", "id": "12" }
]
}
},
"links": {
"self": "http://example.com/articles/1"
}
}],
"included": [{
"type": "people",
"id": "9",
"attributes": {
"firstName": "Dan",
"lastName": "Gebhardt",
"twitter": "dgeb"
},
"links": {
"self": "http://example.com/people/9"
}
}, {
"type": "comments",
"id": "5",
"attributes": {
"body": "First!"
},
"relationships": {
"author": {
"data": { "type": "people", "id": "2" }
}
},
"links": {
"self": "http://example.com/comments/5"
}
}, {
"type": "comments",
"id": "12",
"attributes": {
"body": "I like XML better"
},
"relationships": {
"author": {
"data": { "type": "people", "id": "9" }
}
},
"links": {
"self": "http://example.com/comments/12"
}
}]
}
76 changes: 76 additions & 0 deletions tests/Unit/test_files/json_path_jsonapiB.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{
"links": {
"self": "http://example.com/articles",
"next": "http://example.com/articles?page[offset]=2",
"last": "http://example.com/articles?page[offset]=10"
},
"data": [{
"type": "articles",
"id": "2",
"attributes": {
"title": "JSON:API paints my bikeshed!"
},
"relationships": {
"author": {
"links": {
"self": "http://example.com/articles/2/relationships/author",
"related": "http://example.com/articles/2/author"
},
"data": { "type": "people", "id": "4" }
},
"comments": {
"links": {
"self": "http://example.com/articles/1/relationships/comments",
"related": "http://example.com/articles/1/comments"
},
"data": [
{ "type": "comments", "id": "7" },
{ "type": "comments", "id": "18" }
]
}
},
"links": {
"self": "http://example.com/articles/2"
}
}],
"included": [{
"type": "people",
"id": "4",
"attributes": {
"firstName": "Dan",
"lastName": "Gebhardt",
"twitter": "dgeb"
},
"links": {
"self": "http://example.com/people/4"
}
}, {
"type": "comments",
"id": "7",
"attributes": {
"body": "First!"
},
"relationships": {
"author": {
"data": { "type": "people", "id": "22" }
}
},
"links": {
"self": "http://example.com/comments/5"
}
}, {
"type": "comments",
"id": "18",
"attributes": {
"body": "I like XML better"
},
"relationships": {
"author": {
"data": { "type": "people", "id": "99" }
}
},
"links": {
"self": "http://example.com/comments/18"
}
}]
}
10 changes: 10 additions & 0 deletions tests/Unit/test_files/json_path_simpleA.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"id": 123456,
"title": "Some Title",
"author": "Jane Doe",
"ISBN": "978-0-545-01022-1",
"cover": "https://bucket.foo/bar/123456.webp",
"published": 2017,
"createdAt": "2024-03-08T17:37:09+00:00",
"more": "values"
}
10 changes: 10 additions & 0 deletions tests/Unit/test_files/json_path_simpleB.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"id": 987654,
"title": "Some Title",
"author": "Jane Doe",
"ISBN": "978-0-545-01022-1",
"cover": "https://bucket.foo/bar/987654.webp",
"published": 2017,
"createdAt": "2024-04-02T22:33:44+55:11",
"more": "values"
}

0 comments on commit 7057c51

Please sign in to comment.