PHPUnitでreturnValueMapが上手く動かないときはこれが原因かも

PHPUnitを用いてテストをする際に、
メソッドをモック化する時に明らかにテストが正しいのになぜか失敗してしまう・・
ということがあり、軽くハマったので解決方法を記します。

モック化周りで苦戦している方は参考になるかもしれません。

## PHPUnitでメソッドのモック化（配列マッピングの場合）
今回は、下記の実装内容を想定します。

### 実際のロジック

SampleControllerをテストしたいのですが、`$request->getParam()`への依存を無くすために、
3回呼び出している`$request->getParam()`をモック化しようと思います。

セッションに入れているだけの処理ですが、分かりやすくするために簡略にしています。

```php:SampleController.php
<?php

use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\MessageInterface;

class SampleController
{
	public function action(ServerRequestInterface $request, MessageInterface $response, callable $next)
	{
		// GETパラメータをセッションへ
		$this->session->put('items', [
			'key1' => $request->getParam('key1'),
			'key2' => $request->getParam('key2'),
			'key3' => $request->getParam('key3'),
		]);
		return $next($request, $response);
	}
}
```  

### モック化したいメソッドの内容

```php:Request.php
<?php

use Psr\Http\Message\ServerRequestInterface;

class Request implements ServerRequestInterface
{
  //
    /**
     * Fetch request parameter value from body or query string (in that order).
     * 
     * @param  string $key The parameter key.
     * @param  mixed $default The default value.
     * @return mixed The parameter value.
     */
    public function getParam($key, $default = null)
    {
        $postParams = $this->getParsedBody();
        $getParams = $this->getQueryParams();
        $result = $default;
        if (is_array($postParams) && isset($postParams[$key])) {
            $result = $postParams[$key];
        } elseif (is_object($postParams) && property_exists($postParams, $key)) {
            $result = $postParams->$key;
        } elseif (isset($getParams[$key])) {
            $result = $getParams[$key];
        }

        return $result;
    }
}
```  

### テストファイル
期待される結果に対して`$this->returnValueMap()`を渡すことで、メソッドが複数回呼ばれる場合でも値の指定ができるようになります。
https://phpunit.de/manual/6.5/en/test-doubles.html#test-doubles.stubs.examples.StubTest5.php

```php:SampleControllerTest.php
<?php

use SampleController;
use App\Http\Request;

/**
 *  コントローラー 単体テスト
 */
class SampleControllerTest extends TestCase
{
	public function testAction()
	{
		$request = $this->createMock(Request::class);

		$request->method('getParam')
			->will($this->returnValueMap([
				['key1', 'google'],
				['key2', 'apple'],
				['key3', 'facebook'],
			]));

		$controller = new SampleController();
		$controller->action($request, $this->get('response'), function($request, $response) { return; });
		$this->assertEquals(
			[
				'key1' => 'google',
				'key2' => 'apple',
				'key3' => 'facebook',
			],
			$this->session->get('items')
		);
	}
}
```  

### テスト実行結果
上記で実行すると、なぜか実際の値がnullになってしまい、`returnValueMap()`が上手く動作しません。。  

```
$ vendor/bin/phpunit

There was 1 failure:

1) Tests\Unit\SampleControllerTest::testAction
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
 Array (
-    'key1' => 'google'
-    'key2' => 'apple'
-    'key3' => 'facebook'
+    'key1' => null
+    'key2' => null
+    'key3' => null
 )

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
```  

### 解決方法
`returnValueMap()`の引数には、**モック化するメソッドのすべての引数(任意も含む)を渡さないといけない**ようです。

今回の場合メソッドは`getParam($key, $default = null)`なので、
任意の引数`$default = null`の部分も配列に記載することで解決しました。

#### テストファイル

```diff:SampleControllerTest.php
<?php

use SampleControllerTest;
use App\Http\Request;

/**
 *  コントローラー 単体テスト
 */
class SampleControllerTest extends TestCase
{
	public function testAction()
	{
		$request = $this->createMock(Request::class);

		$request->method('getParam')
			->will($this->returnValueMap([
-				['key1', 'google'],
-				['key2', 'apple'],
-				['key3', 'facebook'],
+				['key1', null, 'google'],
+				['key2', null, 'apple'],
+				['key3', null, 'facebook'],
			]));

		$controller = new SampleControllerTest();
		$controller->action($request, $this->get('response'), function($request, $response) { return; });
		$this->assertEquals(
			[
				'key1' => 'google',
				'key2' => 'apple',
				'key3' => 'facebook',
			],
			$this->session->get('items')
		);
	}
}
```  

こちらに修正してテストを実行したところ、上手く回りました。

メソッドをモック化するときに、
このパターンではなく、別のエラーが起きたよーという方は参考までに教えてください。

それではまた。