PHPUnit のモックで、引数に応じて戻り値を変更する #php #PHPUnit

概要

  • returnValueMap を使うと引数に応じて戻り値を変更できる
  • 例外を返したいときは returnCallback() を使うと便利

returnValueMap を使う

returnValue に [引数, 戻り値] の配列を渡すと、引数に応じた戻り値を返す Mock を作れる。

引数が複数ある場合は、 [引数1, 引数2, 戻り値] のような形式にしてあげればよい。

// Mock を作成
$mock = $this->createMock(HogeClass::class);

// returnValueMap に [引数, 戻り値] の配列を渡す
$mock->method('doSomething')
      ->will($this->returnValueMap(
        [
          ['arg1', 'ret1'],
          ['arg2', 'ret2'],
        ]
      ));

// 引数に応じた値が帰ってくる
$this->assertSame('ret1', $mock->doSomething('arg1'));
$this->assertSame('ret2', $mock->doSomething('arg2'));

例外を返す場合は returnCallback を使うとよさそう

returnValueMap だと通常の戻り値と例外のケースを同時に同じ Mock ができなそう。

返り値を callback にできる returnCallback を応用して実現してみる。

$mock->method('doSomething')
      ->will($this->returnCallback(
        // doSomething が実行されると、callback が呼ばれる
        function ($arg) {
          if ($arg === 'arg1') {
            return 'ret1';
          } elseif ($arg === 'arg2') {
            return 'ret2';
          } elseif ($arg === 'invalid') {
            // 引数によって例外を投げる
            throw new InvalidArgumentException('doSomething called by invalid argumnet. Input was: '.$arg);
          }

          return 'retDefault';
        }
      ));

引数と返り値を切り出す

returnCallback でやりたいことは実現できたのだが、分岐を追記していくスタイルは辛いので、処理を切り分ける。

// 引数と戻り値の紐付けを切り出す
$map = [
  'arg1' => function () { return 'ret1'; }],
  'arg2' => function () { return 'ret1'; }],
  'invalid' => function () { throw new InvalidArgumentException('doSomething called by invalid argumnet. Input was: '.$arg); }],
  'default' => function () { return 'retDefault'; }],
];

$mock->method('doSomething')
      ->will($this->returnCallback(
        function ($arg) {
          if ($map[$arg]) {
            return $map[$arg]();
          }

          return $map['default']();
        }
      ));

補足

そもそも Mock に色々やらせすぎたり、 Mock を使いすぎたりするのは微妙だと思う。

ただ Mock をケースごとに差し替えるのは大変なときにはこういうやり方が使えそう。

参考