Open11

PHPのスナップショットテストのjsonファイルの内容に日本語があるとエンコードされるやつをどうにかする

なおなお

PHPUnitの方は解消するのか

なおなお

雑に書くとこんな感じかな

<?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を使う箇所を置き換える必要がある

https://github.com/spatie/phpunit-snapshot-assertions/blob/5.1.6/src/MatchesSnapshots.php#L92-L95

なので、つぎはこの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のほうはどうだろうか

なおなお

独自クラスを作ること諦めて、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();
});

みたいに使うかな