テスト対象が外部APIにアクセスしている場合、テストの度にAPIにアクセスするためテストに時間がかかる・APIが落ちているとテストも落ちる、といった不便さがあります。そこでモックを使用すると、APIを介すことなく対象のテストを行うことができて便利です。本記事では、Mockeryを使ったモックの作成方法・テストの書き方をご紹介します。

テスト対象クラス

以下のTestApiがテスト対象です。外部APIを介してデータを取得し、リストを返却。

なんらかのエラーでデータが取得できなかった場合にはfalseを返すというシンプルなものです。

class TestApi
{
  private $connection;

  public function __construct(TestOAuth $testOAuth) // ← TestAuthがモックの対象
  {
    $this->connection = $testOAuth;
  }

  public function getList(string $param)
  {
    $list = $this->connection->get($param);

    //データ取得できなかった場合
    if (property_exists($list, 'errors')) {

      return false;
    }

    return $list->statuses;
  }
}

また、モック対象は、TestOAuthです。TestApiのコンストラクタでインジェクションされていますね。

Mockeryでモックしてテスト:正常ケース

テストコード全体は以下のようになります。

    public function getListTest()
    {
    //モックを作成
        $mock = \Mockery::mock(TestOAuth::class);

        $mock->shouldReceive('get')
            ->once()
            ->with('正常test')
            ->andReturn((object) [
                'statuses' =>
                [
                    (object)['data1'],
                    (object)['data2']
                ]
            ]);

        //テストここから
        $testApi = new TestApi($mock);

        $actual = $testApi->getList('正常test');

        $this->assertCount(2, $actual);
    }

モック部分を解説します。

        $mock = \Mockery::mock(TestOAuth::class);

まず最初に、mockメソッドでモックインスタンスを作成します。引数にはモック対象のTestOAuthを渡します。

        $mock->shouldReceive('get')
            ->once()
            ->with('正常test')
            ->andReturn([
                'statuses' =>
                [
                    (object)['data1'],
                    (object)['data2']
                ]
            ]);

次に、作成したモックインスタンスに対してgetメソッドが呼ばれた際の振る舞いを指定しています。

onceは、メソッドが1度だけ呼ばれること。

withは、getメソッドに渡される引数が「正常test」であること。

そして、最後のandReturnではAPIからのレスポンスにあたる部分で、このモックでは2つのオブジェクトを含む配列を持つstatusesプロパティが返るようにしています。

また、モックインスタンスは作成しただけではテストに反映されません。以下のようにコンストラクタに渡すことでTestApiのnew時にDIされ、実際のTestOAuthではなくモックが使用されるようになります。

       $testApi = new TestApi($mock);

テストは以下のように、コマンドラインで実行します。

 $ vendor/bin/phpunit --filter getListTest
PHPUnit 9.5.16 by Sebastian Bergmann and contributors.

.                                                                1 / 1 (100%)

Time: 00:00.131, Memory: 22.00 MB

無事テストが通ったので、モックに設定したふるまい(getが1度呼ばれる・引数の中身)が正常であったことが確認できました。

Mockeryでモックしてテスト:エラーケース

次は、エラーケースのテストになります。テスト対象メソッドの以下にあたる箇所ですね。

    //データ取得できなかった場合
    if (property_exists($list, 'errors')) {

      return false;
    }

APIからのレスポンスにerrorsというプロパティが含まれている場合に、正しくfalseが返るかをテストします。

そのため、モック作成部分では以下のように指定します。

        $mock = \Mockery::mock(TestOAuth::class);

        $mock->shouldReceive('get')
            ->once()
            ->with('エラーtest')
            ->andReturn(['errors' => []]);

モックオブジェクトの作成やgetが1度呼ばれるという箇所は同じですが、andReturnで返り値にerrorsプロパティを含むように記述しています。

モック部分を含めたテストの全文は以下のようになります。

    public function getListTest_error()
    {
        $mock = \Mockery::mock(TestOAuth::class);

        $mock->shouldReceive('get')
            ->once()
            ->with('エラーtest')
            ->andReturn(['errors' => []]);

        $testApi = new TestApi($mock);

        $actual = $testApi->getList('エラーtest');

        $this->assertEquals(false, $actual);
    }

これでテストを実行しOKであれば、呼ばれたモックオブジェクトの振る舞いが期待通りかに加え、正しくfalseが返っていることが確認できます。

By hmatsu