MeWrite Docs

Laravel Mockery: method does not exist on this mock object

Laravel + Mockeryでメソッドが存在しないエラーが発生する場合の原因と解決策。actingAsとMockの順序問題

概要

Laravel + Mockeryでテストを書く際、Mockが正しく設定されているはずなのに「method does not exist on this mock object」エラーが発生する問題。特に $this->actingAs() との組み合わせで起きやすい、見つけにくいバグです。

エラーメッセージ

Mockery\Exception\BadMethodCallException: Method Mockery_0_App_Services_SomeService::someMethod() does not exist on this mock object

原因

  1. actingAsとMockの順序問題: actingAs() の後にMockをバインドしている
  2. サービスコンテナの解決タイミング: 認証時に依存関係が先に解決される
  3. Mockのバインドが遅い: 既に解決済みのインスタンスは置き換わらない

なぜ見つけにくいのか

  • 他のテストからコードをコピーしても動かない
  • エラーメッセージからは順序問題と分からない
  • actingAs() の位置を意識することが少ない
  • 公式ドキュメントに明記されていない

解決策

1. Mockを actingAs より先にバインドする

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// ❌ NG: actingAs の後に Mock をバインド
public function test_something_wrong(): void
{
    $user = User::factory()->create();
    $this->actingAs($user);  // ← ここで依存関係が解決される

    $mock = Mockery::mock(SomeService::class);
    $mock->shouldReceive('someMethod')->andReturn('result');
    $this->app->instance(SomeService::class, $mock);  // ← 手遅れ

    // テスト実行 → エラー
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// ✅ OK: Mock を先にバインドしてから actingAs
public function test_something_correct(): void
{
    $user = User::factory()->create();

    // 先に Mock をバインド
    $mock = Mockery::mock(SomeService::class);
    $mock->shouldReceive('someMethod')->andReturn('result');
    $this->app->instance(SomeService::class, $mock);

    $this->actingAs($user);  // ← Mock バインド後に認証

    // テスト実行 → 成功
}

2. setUp() でMockをバインドする

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class SomeFeatureTest extends TestCase
{
    protected SomeService|MockInterface $mockService;

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

        // setUp で先にバインド
        $this->mockService = Mockery::mock(SomeService::class);
        $this->app->instance(SomeService::class, $this->mockService);
    }

    public function test_with_authenticated_user(): void
    {
        $user = User::factory()->create();

        $this->mockService
            ->shouldReceive('someMethod')
            ->andReturn('result');

        $this->actingAs($user)
            ->get('/some-endpoint')
            ->assertOk();
    }
}

3. partialMock を使う(Laravel 8+)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public function test_with_partial_mock(): void
{
    $user = User::factory()->create();

    // partialMock は自動的にコンテナにバインドされる
    $this->partialMock(SomeService::class, function ($mock) {
        $mock->shouldReceive('someMethod')->andReturn('result');
    });

    $this->actingAs($user)
        ->get('/some-endpoint')
        ->assertOk();
}

4. mock() ヘルパーを使う

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public function test_with_mock_helper(): void
{
    $user = User::factory()->create();

    // mock ヘルパーも自動バインド
    $this->mock(SomeService::class, function ($mock) {
        $mock->shouldReceive('someMethod')->andReturn('result');
    });

    $this->actingAs($user)
        ->get('/some-endpoint')
        ->assertOk();
}

原因の詳細

サービスコンテナの解決タイミング

1. actingAs($user) を呼ぶ
2. 認証ガードが初期化される
3. ガードの依存関係がサービスコンテナから解決される
4. この時点で SomeService の実インスタンスが生成・キャッシュされる
5. 後から Mock をバインドしても、既に解決済みのインスタンスは使われ続ける

なぜ他のテストからコピーしても動かないのか

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// テストA: 動く(認証なし)
public function test_without_auth(): void
{
    $mock = Mockery::mock(SomeService::class);
    $mock->shouldReceive('someMethod')->andReturn('result');
    $this->app->instance(SomeService::class, $mock);

    $this->get('/public-endpoint')->assertOk();  // 動く
}

// テストB: 動かない(テストAからコピーして認証を追加)
public function test_with_auth(): void
{
    $user = User::factory()->create();
    $this->actingAs($user);  // ← これを追加したら壊れる

    $mock = Mockery::mock(SomeService::class);
    $mock->shouldReceive('someMethod')->andReturn('result');
    $this->app->instance(SomeService::class, $mock);

    $this->get('/protected-endpoint')->assertOk();  // エラー!
}

デバッグ方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// コンテナにバインドされているか確認
public function test_debug(): void
{
    $mock = Mockery::mock(SomeService::class);
    $this->app->instance(SomeService::class, $mock);

    // バインド後に確認
    $resolved = $this->app->make(SomeService::class);
    dump(get_class($resolved));  // Mockery_0_... ならOK

    $this->actingAs($user);

    // actingAs 後に再確認
    $resolvedAfter = $this->app->make(SomeService::class);
    dump(get_class($resolvedAfter));  // 変わっていないか確認
}

よくある間違い

  • actingAs() の後に Mock をバインドする
  • shouldReceive() でメソッド名をタイポ
  • 部分モックなのに makePartial() を忘れる
  • singleton でバインドされているサービスを instance でバインドしようとする

関連エラー

関連エラー

PHP の他のエラー

最終更新: 2025-12-11