Mockery with() でメソッドがObjectを渡す検証でNoMatchingExpectationException


with() にObjectを渡す検証でNoMatchingExpectationExceptionが出る

mockしたRepositoryクラスのメソッドの呼び出しの検証をするServiceTest.phpを作りました。

    public function test_reset_case1_Entityのインスタンスでfail_countが0のデータがrepositoryのresetCountの処理に渡される()
    {
        $test_id = 1;
        $expected = new Entity([
            'fail_count'=>0,
        ]);
        $mock_Repository = \Mockery::mock(Repository::class);

        $mock_Repository->shouldReceive('resetCount')
                           ->once()
                           ->with($test_id, $expected);
        $Service = new Service($mock_Repository);
        $Service ->reset($test_id);
    }

Service の resetでRepository のメソッドresetCountを呼ぶ検証でMockeryのwith()を使って引数の検証をしています。このテストが下記のようなテスト結果となりました。

1) Tests\Unit\ServiceTest::test_reset_case1_Entityのインスタンスでfail_countが0のデータがrepositoryのresetCountの処理に渡される
Mockery\Exception\NoMatchingExpectationException: No matching handler found for Mockery_0_App_Repository::resetCount(1, object(App\Entity)). Either the method was unexpected or its arguments matched no expected argument list for this method

Objects: ( array (
  'App\\Entity' =>
  array (
    'class' => 'App\\Entity,
    'properties' =>
    array (
    ),
  ),
))

(最初はExceptionのメッセージからmockオブジェクトをLaravelのコンストラクタインジェクションできないのかと思ってしまったのですが、原因は違いました。)

原因: with()の引数にオブジェクトがそのまま渡せない

原因はwith()での検証にobjectをそのまま渡していたことが原因でした。
https://github.com/mockery/mockery/issues/328

代わりにMockery::onで引数の検証を行うクロージャを渡す

https://laradevtips.com/2018/12/01/unit-tests-complex-argument-matching-with-mockeryon-the-right-way/
オブジェクトをwithに渡す場合はMockery::onでtrueを返すclosureを渡し、その中で引数の検証ができるようでした。

    public function test_reset_case1_Entityのインスタンスでfail_countが0のデータがrepositoryのresetCountの処理に渡される()
    {
        $test_id = 1;       
        $expected = new Entity([
            'fail_count'=>0,
        ]);
        $mock_Repository = \Mockery::mock(Repository::class);

        $mock_Repository->shouldReceive('resetCount')
                            ->once()
                            ->with($test_id, \Mockery::on(function($actual) use ($expected) {
                                $this->assertInstanceOf(Entity::class, $actual);
                                $this->assertEquals($expected, $actual);
                                return true;
                            }));
        $Service = new Service($mock_Repository);
        $Service ->reset($test_id);
    }

これでテストが通りました。

...                                                                 1 / 1 (100%)

Time: 1.05 seconds, Memory: 16.00 MB

OK (1 tests, 3 assertions)