laravel Mockeryを使ってテストを書く（外部APIアクセスをモック化）

# 概要

- laravelのUnitテストにて外部API実行関数の戻り値をMockeryを使って定義する方法を簡単にまとめる。

# ご注意

- 今回は汎用的にまとめようと思う。従って記事のコードをそのままコピペしても動作しないと思われる。

# 前提

- 下記の方法でMockeryのインストールを済ませておく。
    - [laravel Mockeryを使ってテストを書く（インストール準備編）](https://qiita.com/miriwo/items/0f8fdf91177f6c3cb713)

# 方法

1. artisanコマンドを用いて任意のUnitテストのクラスを用意する。

    ```terminal
    $ php artisan make:test --unit FooTest
    ```

1. UnitテストクラスのsetUp()関数内でモック化したい関数が所属するクラスを指定してテストダブルを定義する。

    ```FooTest.php
    <?php

    namespace Tests\Unit;

    use PHPUnit\Framework\TestCase;
    use Mockery;
    use モック化したい関数が所属するクラス;

    class AccessYoutubeApiServiceTest extends TestCase
    {
        private モック化したい関数が所属するクラス $xxx;

        public function setUp(): void
        {
            parent::setUp();
            
            // Mockeryを用いたモックの設定
            $this->xxx = Mockery::mock(モック化したい関数が所属するクラス::class);
        }
    }
    ```

1. モック化したい関数の戻り値の定義（今回は配列で定義しているが、モック化したい関数に合わせて任意の戻り値を設定する。）

    ```FooTest.php
    <?php

    namespace Tests\Unit;

    use PHPUnit\Framework\TestCase;
    use Mockery;
    use モック化したい関数が所属するクラス;

    class AccessYoutubeApiServiceTest extends TestCase
    {
        private モック化したい関数が所属するクラス $xxx;
        private array $expected_return;

        public function setUp(): void
        {
            parent::setUp();

            $this->expected_return = [
                'items' => [
                    [
                        'id' => 1,
                        'name' => 'hoge hoge',
                    ],
                    [
                        'id' => 2,
                        'name' => 'fuga fuga',
                    ],
                ],
            ];

            // Mockeryを用いたモックの設定
            $this->xxx = Mockery::mock(モック化したい関数が所属するクラス::class);
        }
    }
    ```

1. モック化したい関数そのものの・引数・先に定義したモックの戻り値を設定する。

    ```FooTest.php
    <?php

    namespace Tests\Unit;

    use PHPUnit\Framework\TestCase;
    use Mockery;
    use モック化したい関数が所属するクラス;

    class AccessYoutubeApiServiceTest extends TestCase
    {
        private モック化したい関数が所属するクラス $xxx;
        private array $expected_return;

        public function setUp(): void
        {
            parent::setUp();

            $this->expected_return = [
                'items' => [
                    [
                        'id' => 1,
                        'name' => 'hoge hoge',
                    ],
                    [
                        'id' => 2,
                        'name' => 'fuga fuga',
                    ],
                ],
            ];

            // Mockeryを用いたモックの設定
            $this->xxx = Mockery::mock(モック化したい関数が所属するクラス::class);
            $this->xxx
                ->shouldReceive('モック化したい関数名')
                ->with(モック化したい関数の引数)
                ->andReturn(モックの戻り値、今回はの場合$this->expected_returnとなる);
            }
    }
    ```

1. モック化したい関数が所属するクラスに合わせてモックを注入する。（こちらは皆さんのテスト対象のクラスなどに合わせて記載してください。）

    ```FooTest.php
    <?php

    namespace Tests\Unit;

    use PHPUnit\Framework\TestCase;
    use Mockery;
    use モック化したい関数が所属するクラス;
    use テスト対象クラス;

    class AccessYoutubeApiServiceTest extends TestCase
    {
        private テスト対象クラス $PiyoPiyoService;
        private モック化したい関数が所属するクラス $xxx;
        private array $expected_return;

        public function setUp(): void
        {
            parent::setUp();

            $this->expected_return = [
                'items' => [
                    [
                        'id' => 1,
                        'name' => 'hoge hoge',
                    ],
                    [
                        'id' => 2,
                        'name' => 'fuga fuga',
                    ],
                ],
            ];

            // Mockeryを用いたモックの設定
            $this->xxx = Mockery::mock(モック化したい関数が所属するクラス::class);
            $this->xxx
                ->shouldReceive('モック化したい関数名')
                ->with(モック化したい関数の引数)
                ->andReturn(モックの戻り値、今回はの場合$this->expected_returnとなる);
            }

        $this->piyoPiyoService = new PiyoPiyoService($this->xxx);
    }
    ```

1. あとは`$this->piyoPiyoService`を用いて各種テストを行えばよいはずである。

# 特記事項

- 一度設定したモックの関数の戻り値は上書きする事ができない。
    - 例えばテストクラスのsetUp()関数内で下記の様にモックの戻り値を設定下とする。（Xxxクラスのbar関数を引数100で実行した場合101が返るモック設定）

        ```php
        $this->xxx
            ->shouldReceive('bar')
            ->with(100)
            ->andReturn(101);
        }
        ```

    - その後そのテストクラスのテスト関数内で下記の様な記載をしてモックの戻り値の再定義を試みたとする。（Xxxクラスのbar関数を引数100で実行した場合201が返るモック設定に更新）

        ```php
        $this->xxx
            ->shouldReceive('bar')
            ->with(100)
            ->andReturn(201);
        }
        ```

    - しかしモックの設定は上書きする事ができないので先にsetUp()内で定義した「Xxxクラスのbar関数を引数100で実行した場合101が返るモック設定」が生きてしまう。
    - ただし、これはメソッドチェーンのwith()で指定している引数が一致していた場合上書きできない。
    - 下記のようにテスト関数内で記載したら別の戻り値の設定として設定し直すことは可能だった気がする。（Xxxクラスのbar関数を引数101で実行した場合201が返るモック設定）

        ```php
        $this->xxx
            ->shouldReceive('bar')
            ->with(101)
            ->andReturn(201);
        }
        ```

    - 引数が違えば全く違う設定としてモックに設定することができる。
    - 逆にテストクラスのsetUp()関数内で引数を指定せずにモックの戻り値を設定してしまうとあとからいくらテスト関数内でwith()関数で引数を指定して戻り値の設定をしてもsetUp()関数内での戻り値設定が勝ってしまうらしい。
    - setUp()関数内で下記の様にXxxクラスのbar()関数の戻り値を引数なしで設定してしまうと

        ```php
        $this->xxx
            ->shouldReceive('bar')
            ->andReturn(101);
        }
        ```

    - テスト関数内でいくら下記の様に記載しても引数無しのsetUp()関数での戻り値設定が生きてしまう。

        ```php
        $this->xxx
            ->shouldReceive('bar')
            ->with(101)
            ->andReturn(201);
        }
        ```

    - setUp()関数内でのモック定義はなるべく引数も含めて設定しよう。（「Xxxクラスのbar()関数が実行された時の戻り地はこれ！」ではなく「Xxxクラスのbar()関数が引数100で実行された時の戻り地はこれ！」のように設定）