🕌

Laravel 作成したカスタムルールに対してテストを書いてみる

2022/03/04に公開

前書き

前回と前々回に作成したカスタムルールに対して、テストを書いてみたいと思います。
(フォームリクエストとかに対するものではなく)

最終形のカスタムルールは、下の方に掲載しておきます。

その1:前々回
https://zenn.dev/nshiro/articles/225a84621535f2

その2:前回
https://zenn.dev/nshiro/articles/d66e45bbc3cbb2

本題

テストのファイルは以下のコマンドで作成します。

php artisan make:test Rules/AllRequiredRuleTest --unit

今回は、Laravel の世界を構築する必要は無くて済むので、--unit を付けています。(PHPUnit\Framework\TestCase を直接継承させます)

で、tests/Unit/Rules/AllRequiredRuleTest.php ファイルが生成されましたので、以下の感じで書いてやります。

<?php

namespace Tests\Unit\Rules;

use App\Rules\AllRequired;
use Illuminate\Translation\ArrayLoader;
use Illuminate\Translation\Translator;
use Illuminate\Validation\Validator;
use PHPUnit\Framework\TestCase;

class AllRequiredRuleTest extends TestCase
{
    /** @test */
    function AllRequired、全て埋まってないのでvalidationエラー()
    {
        $trans = $this->getTranslator();

        $rules = ['foo1' => new AllRequired('foo2', 'foo3')];
        $this->fails(new Validator($trans, [], $rules));
        $this->fails(new Validator($trans, ['foo1' => 'ほげ'], $rules));
        $this->fails(new Validator($trans, ['foo1' => 'ほげ', 'foo2' => null], $rules));
        $this->fails(new Validator($trans, ['foo1' => 'ばー', 'foo2' => 'ばず'], $rules));
        $this->fails(new Validator($trans, ['foo2' => 'ばー'], $rules));
        $this->fails(new Validator($trans, ['foo3' => 'ばず'], $rules));
        $this->fails(new Validator($trans, ['foo2' => 'ばー', 'foo3' => 'ばず'], $rules));

        $rules = ['foo1' => new AllRequired(['foo2', 'foo3'])]; // 配列で指定
        $this->fails(new Validator($trans, ['foo1' => 'ばー', 'foo2' => 'ばず'], $rules));
    }

    /** @test */
    function AllRequired、全て埋まっているのでvalidation、OK()
    {
        $trans = $this->getTranslator();

        $rules = ['foo1' => new AllRequired('foo2', 'foo3')];
        $this->passes(new Validator($trans, ['foo1' => 'ほげ', 'foo2' => 'ばー', 'foo3' => 'ばず'], $rules));

        $rules = ['foo1' => new AllRequired(['foo2', 'foo3'])]; // 配列で指定
        $this->passes(new Validator($trans, ['foo1' => 'ほげ', 'foo2' => 'ばー', 'foo3' => 'ばず'], $rules));
    }

    /** @test */
    function AllRequired、デフォルトのエラーメッセージがセットされる()
    {
        $trans = $this->getTranslator();

        $rules = ['foo1' => new AllRequired('foo2')];

        $this->assertSame(
            ['foo1' => ['foo1を入力してください。']],
            (new Validator($trans, [], $rules))->messages()->toArray()
        );
    }

    /** @test */
    function AllRequired、setMessageでエラーメッセージがセットされる()
    {
        $trans = $this->getTranslator();

        $rules = ['foo1' => (new AllRequired('foo2'))->setMessage('全て必須です。')];

        $this->assertSame(
            ['foo1' => ['全て必須です。']],
            (new Validator($trans, [], $rules))->messages()->toArray()
        );
    }

    /** @test */
    function AllRequired、skipIfのテスト()
    {
        $trans = $this->getTranslator();

        $rules = ['foo1' => (new AllRequired('foo2'))->skipIf(true)];
        $this->passes(new Validator($trans, [], $rules));

        $rules = ['foo1' => (new AllRequired('foo2'))->skipIf(fn () => true)];
        $this->passes(new Validator($trans, [], $rules));

        $rules = ['foo1' => (new AllRequired('foo2'))->skipIf(false)];
        $this->fails(new Validator($trans, [], $rules));

        $rules = ['foo1' => (new AllRequired('foo2'))->skipIf(fn () => false)];
        $this->fails(new Validator($trans, [], $rules));
    }

    protected function fails($validator)
    {
        $this->assertFalse($validator->passes());
    }

    protected function passes($validator)
    {
        $this->assertTrue($validator->passes());
    }

    protected function getTranslator()
    {
        return new Translator(
            new ArrayLoader, 'en'
        );
    }
}

大体、ご覧いただいた通りです🙇‍♂️。一部、Laravel本体のソースなども参考にしております。

テスト対象のカスタムルールは下記です。

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\DataAwareRule;
use Illuminate\Contracts\Validation\ImplicitRule;

class AllRequired implements ImplicitRule, DataAwareRule
{
    public bool $skip = false;

    protected array $others = [];

    protected string $message = ":attributeを入力してください。";

    /**
     * Create a new rule instance.
     *
     * @return void
     */
    public function __construct($others)
    {
        $this->others = is_array($others) ? $others : func_get_args();
    }

    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        if ($this->skip) {
            return true;
        }

        $fields = array_merge([$attribute], $this->others);

        foreach($fields as $field) {
            if (! isset($this->data[$field]) || blank($this->data[$field])) {
                return false;
            }
        }

        return true;
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return $this->message;
    }

    /**
     * エラーメッセージを設定する
     *
     * @param string $message
     * @return object $this
     */
    public function setMessage($message)
    {
        $this->message = $message;

        return $this;
    }

    /**
     * Set the data under validation.
     *
     * @param  array  $data
     * @return $this
     */
    public function setData($data)
    {
        $this->data = $data;

        return $this;
    }

    /**
     * $callback が true の時は、validationを走らせない
     *
     * @param mixed $callback
     * @return object $this
     */
    public function skipIf($callback)
    {
        $this->skip = !! value($callback);

        return $this;
    }
}

雑感

3回の連続ものにすると、「もう読んでくれている方もいないのではないか…」と思ったりしますが😅、めげずに書いてみました。

おかしな箇所等ありましたら、コメント下さい。

Discussion