Closed11
PHPのスナップショットテストのjsonファイルの内容に日本語があるとエンコードされるやつをどうにかする
この記事の話
スナップショットのjsonファイルの内容に日本語があるとエンコードされるから解消してみる
解決策はjson_encodeの第2引数にJSON_UNESCAPED_UNICODE
を渡すだけ
PHPUnitの方は解消するのか
スナップショットのjsonファイルの中身はここで作っている
なので、JsonDriverを継承してserializeメソッドを拡張すれば解決するはず
雑に書くとこんな感じかな
<?php
declare(strict_types=1);
namespace Tests;
use Spatie\Snapshots\Drivers\JsonDriver as SnapshotJsonDriver;
use Spatie\Snapshots\Exceptions\CantBeSerialized;
class JsonDriver extends SnapshotJsonDriver
{
public function serialize($data): string
{
if (is_string($data)) {
$data = json_decode($data);
}
if (is_resource($data)) {
throw new CantBeSerialized('Resources can not be serialized to json');
}
return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)."\n";
}
}
違いはjson_encodeをしているところだけ、第2引数にJSON_UNESCAPED_UNICODE
を追加した
+ return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)."\n";
- return json_encode($data, JSON_PRETTY_PRINT)."\n";
つぎにJsonDriverを使う箇所を置き換える必要がある
なので、つぎはこのtraitを拡張しないといけないわけだ
<?php
declare(strict_types=1);
namespace Tests;
use Spatie\Snapshots\MatchesSnapshots;
trait MyMatchesSnapshots
{
use MatchesSnapshots;
public function assertMatchesJsonSnapshot(array|string|null|int|float|bool $actual): void
{
$this->assertMatchesSnapshot($actual, new MyJsonDriver());
}
}
これで上書きできたはず
テストクラスに拡張したtraitを使うように変えると
<?php
declare(strict_types=1);
namespace Tests;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
class SnapshotTest extends TestCase
{
use MyMatchesSnapshots;
#[Test]
public function snapshot(): void
{
$this->assertMatchesJsonSnapshot(json_render());
}
}
これで動くはず
./vendor/bin/phpunit tests/SnapshotTest.php
PHPUnit 11.3.1 by Sebastian Bergmann and contributors.
Runtime: PHP 8.3.10
I 1 / 1 (100%)
Time: 00:00.005, Memory: 8.00 MB
OK, but there were issues!
Tests: 1, Assertions: 3, Incomplete: 1.
cat tests/__snapshots__/SnapshotTest__snapshot__1.json
[
{
"name": "りんご",
"price": 150,
"stock": 10
},
{
"name": "バナナ",
"price": 200,
"stock": 5
},
{
"name": "みかん",
"price": 120,
"stock": 8
}
]
でけた、PHPUnitはこれでok
Pestのほうはどうだろうか
json_encodeをしているのはおそらくここだ
なので、このメソッドを拡張すればok
って思ったけど、finalキーワードがついているクラスだから拡張ができないか
独自クラスを作ること諦めて、match式に注目すると
$string = match (true) {
is_string($this->value) => $this->value,
is_object($this->value) && method_exists($this->value, 'toSnapshot') => $this->value->toSnapshot(),
is_object($this->value) && method_exists($this->value, '__toString') => $this->value->__toString(),
is_object($this->value) && method_exists($this->value, 'toString') => $this->value->toString(),
$this->value instanceof \Illuminate\Testing\TestResponse => $this->value->getContent(), // @phpstan-ignore-line
is_array($this->value) => json_encode($this->value, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT),
$this->value instanceof Traversable => json_encode(iterator_to_array($this->value), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT),
$this->value instanceof JsonSerializable => json_encode($this->value->jsonSerialize(), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT),
is_object($this->value) && method_exists($this->value, 'toArray') => json_encode($this->value->toArray(), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT),
default => InvalidExpectationValue::expected('array|object|string'),
};
いろいろあるけど、一番楽なのは、
はじめのis_string($this->value) => $this->value,
だから、はじめからjson_encodeをした状態の文字列を渡すのがいいのかもしれない
あとは、この3つのどれかのメソッドを実装した値を渡すかですな
is_object($this->value) && method_exists($this->value, 'toSnapshot') => $this->value->toSnapshot(),
is_object($this->value) && method_exists($this->value, '__toString') => $this->value->__toString(),
is_object($this->value) && method_exists($this->value, 'toString') => $this->value->toString(),
class Value
{
public function __construct(
private array $value
) {
}
public function toSnapshot(): string
{
return json_encode($this->$value, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
}
みたいなクラスを用意して、
<?php
declare(strict_types=1);
test('Snapshot-tests', function () {
expect(new Value(json_render()))->toMatchSnapshot();
});
みたいに使うかな
このスクラップは10日前にクローズされました