MeWrite Docs

PHPUnit: Expectation failed

PHPUnitでモックの期待値が満たされなかった場合のエラー

概要

PHPUnitでモックオブジェクトの期待値(呼び出し回数、引数など)が満たされなかった場合に発生するエラーです。

エラーメッセージ

Expectation failed for method name is "send" when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.

または

PHPUnit\Framework\ExpectationFailedException: Failed asserting that 'actual' matches expected 'expected'.

原因

  1. モックの設定ミス: メソッド名やインターフェースの誤り
  2. 呼び出し回数: 期待した回数と実際の呼び出し回数の不一致
  3. 引数の不一致: 期待した引数と実際の引数の違い
  4. 依存性注入: モックが正しく注入されていない

解決策

1. 基本的なモック

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
use PHPUnit\Framework\TestCase;

class UserServiceTest extends TestCase
{
    public function testCreateUser(): void
    {
        // モックを作成
        $repository = $this->createMock(UserRepository::class);

        // 期待値を設定
        $repository->expects($this->once())
            ->method('save')
            ->with($this->isInstanceOf(User::class))
            ->willReturn(true);

        $service = new UserService($repository);
        $result = $service->createUser('John', 'john@example.com');

        $this->assertTrue($result);
    }
}

2. 呼び出し回数の指定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 1回だけ
$mock->expects($this->once())->method('call');

// 複数回
$mock->expects($this->exactly(3))->method('call');

// 1回以上
$mock->expects($this->atLeastOnce())->method('call');

// 0回以上(呼ばれなくてもOK)
$mock->expects($this->any())->method('call');

// 呼ばれてはいけない
$mock->expects($this->never())->method('call');

3. 引数のマッチング

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 完全一致
$mock->expects($this->once())
    ->method('find')
    ->with(1)
    ->willReturn($user);

// 型チェック
$mock->expects($this->once())
    ->method('save')
    ->with($this->isInstanceOf(User::class));

// コールバックで検証
$mock->expects($this->once())
    ->method('update')
    ->with($this->callback(function ($user) {
        return $user->getName() === 'John';
    }));

// 複数引数
$mock->expects($this->once())
    ->method('query')
    ->with('SELECT * FROM users', [], 100);

4. 戻り値の設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 固定値を返す
$mock->method('find')->willReturn($user);

// 引数に応じて返す
$mock->method('find')
    ->willReturnMap([
        [1, $user1],
        [2, $user2],
    ]);

// コールバックで返す
$mock->method('process')
    ->willReturnCallback(function ($input) {
        return strtoupper($input);
    });

// 連続した戻り値
$mock->method('next')
    ->willReturnOnConsecutiveCalls('first', 'second', 'third');

// 例外をスロー
$mock->method('find')
    ->willThrowException(new NotFoundException());

5. 部分モック

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 一部のメソッドだけモック
$service = $this->getMockBuilder(UserService::class)
    ->onlyMethods(['sendEmail'])
    ->setConstructorArgs([$repository])
    ->getMock();

$service->method('sendEmail')->willReturn(true);

// 実際のメソッドは動作する
$result = $service->createUser('John');

6. スパイパターン

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public function testEventDispatched(): void
{
    $dispatcher = $this->createMock(EventDispatcher::class);

    // 呼び出しをキャプチャ
    $capturedEvent = null;
    $dispatcher->expects($this->once())
        ->method('dispatch')
        ->willReturnCallback(function ($event) use (&$capturedEvent) {
            $capturedEvent = $event;
            return $event;
        });

    $service = new UserService($dispatcher);
    $service->createUser('John');

    // キャプチャした引数を検証
    $this->assertInstanceOf(UserCreatedEvent::class, $capturedEvent);
    $this->assertEquals('John', $capturedEvent->getName());
}

7. プロフェシー(代替)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
use Prophecy\PhpUnit\ProphecyTrait;

class UserServiceTest extends TestCase
{
    use ProphecyTrait;

    public function testWithProphecy(): void
    {
        $repository = $this->prophesize(UserRepository::class);

        $repository->save(Argument::type(User::class))
            ->shouldBeCalled()
            ->willReturn(true);

        $service = new UserService($repository->reveal());
        $service->createUser('John');
    }
}

8. データプロバイダー

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
/**
 * @dataProvider userDataProvider
 */
public function testCreateUserWithDifferentData(
    string $name,
    string $email,
    bool $expected
): void {
    // テストロジック
    $this->assertEquals($expected, $result);
}

public static function userDataProvider(): array
{
    return [
        'valid user' => ['John', 'john@example.com', true],
        'invalid email' => ['John', 'invalid', false],
        'empty name' => ['', 'test@example.com', false],
    ];
}

9. 例外のテスト

1
2
3
4
5
6
7
8
public function testThrowsException(): void
{
    $this->expectException(InvalidArgumentException::class);
    $this->expectExceptionMessage('Email is required');

    $service = new UserService($this->createMock(UserRepository::class));
    $service->createUser('John', '');
}

10. セットアップとティアダウン

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
private UserRepository $repository;
private UserService $service;

protected function setUp(): void
{
    parent::setUp();
    $this->repository = $this->createMock(UserRepository::class);
    $this->service = new UserService($this->repository);
}

protected function tearDown(): void
{
    // クリーンアップ
    parent::tearDown();
}

よくある間違い

  • expects()method() の順序を間違える
  • インターフェースではなく具象クラスをモック(可能だが非推奨)
  • with() で配列を渡す際に $this->equalTo() を忘れる
  • 依存するモックを実際のオブジェクトに渡し忘れる

最終更新: 2025-12-09