Mockeryのパーシャルモックを試してみた

# これは何
- laravel で Mockery を使ったパーシャルモックの作り方について試してみたものです。

# まとめ
- 最初にまとめを書いておきます。

```php
// makePartialで、パーシャルモックになる。
$m = \Mockery::mock('App\Libs\MyHello')->makePartial();

// makePartialすると **全て** のメソッドが元クラスのメソッドに中継されるので
// 明示的に、中継メソッドを指定したい場合は、passthru を使う。
$m->shouldReceive('dog')->passthru();

// クラス名を間違えて(うっかりエルが多すぎた)もエラーにならず、メソッドなし空モックができてしまうので、
$m = \Mockery::mock('App\Libs\MyHellllllo')->makePartial();

// クラス指定は ::class（完全修飾したクラス名を得ることができる）を使ったほうが間違いが無い。
//   PHP: クラスの基礎 - Manual
//   http://www.php.net/manual/ja/language.oop5.basic.php#language.oop5.basic.class.class
$m = \Mockery::mock(MyHello::class)->makePartial();

// コンストラクタを呼びたい場合、第2パラメータで指定すればよい。
$m = \Mockery::mock('App\Libs\MyHello', ['aa','コンストラクタの引数'])->makePartial();
```

# 試してみた
- 詳しくはこんな感じでした。

## ソース

```php:app/Libs/MyHello.php
<?php
namespace App\Libs;

class MyHello
{
    function __construct($msg)
    {
        echo "Hello->__construct({$msg})\n";
    }

    public function dog($msg)
    {
        echo "Hello->dog({$msg})\n";
    }

    public function cat($msg)
    {
        echo "Hello->cat({$msg})\n";
    }
}
```

```php:tests/Unit/MyHelloTest.php
<?php

namespace Tests\Unit;

use Tests\TestCase;
use App\Libs\MyHello;

class MyHelloTest extends TestCase
{

    /**
     * 普通にパーシャルモックを作る
     */
    public function test10()
    {
        echo "===".__METHOD__."\n";
        $m = \Mockery::mock('App\Libs\MyHello', ["CONSTRACT"]);

        // モックメソッドがない場合、オリジナルのメソッドに中継
        $m->makePartial(); 

        // cat() のモックメソッド定義
        $m->shouldReceive('cat')->with(\Mockery::on(function ($msg) {
              echo "Hello->cat({$msg}) MOCK\n";
              return true;
          }));

        echo get_class($m) . "\n";
        $m->dog('DOG'); // オリジナルのdog()をcall
        $m->cat('CAT'); // 上記のモックのcat()をcall
        $this->assertTrue(true);
    }

    /**
     * makePartial() を使わなくても
     * passthru() でパーシャルモックを作れる。
     */
    public function test11()
    {
        echo "===".__METHOD__."\n";
        $m = \Mockery::mock('App\Libs\MyHello', ["CONSTRACT"]);

        // dog() をオリジナルのdog()に中継
        $m->shouldReceive('dog')->passthru();

        // cat() のモックメソッド定義
        $m->shouldReceive('cat')->with(\Mockery::on(function ($msg) {
              echo "Hello->cat({$msg}) MOCK\n";
              return true;
          }));

        echo get_class($m) . "\n";
        $m->dog('DOG'); // オリジナルのdog()をcall
        $m->cat('CAT'); // 上記のモックのcat()をcall
        $this->assertTrue(true);
    }

    /**
     * オリジナルのクラスの指定は、文字列('App\Libs\MyHello')以外にも
     * MyHello::class でも指定できる。
     */
    public function test12()
    {
        echo "===".__METHOD__."\n";
        $m = \Mockery::mock(MyHello::class, ["CONSTRACT"])
          ->makePartial();
        $m->shouldReceive('cat')->with(\Mockery::on(function ($msg) {
              echo "Hello->cat({$msg}) MOCK\n";
              return true;
          }));

        echo get_class($m) . "\n";
        $m->dog('DOG');
        $m->cat('CAT');
        $this->assertTrue(true);
    }

    /**
     * オブジェクト(new クラス名)でも指定できる。
     */
    public function test20()
    {
        echo "===".__METHOD__."\n";
        $m = \Mockery::mock(new MyHello("CONSTRACT"));
        $m->shouldReceive('cat')->with(\Mockery::on(function ($msg) {
              echo "Hello->cat({$msg}) MOCK\n";
              return true;
          }));

        echo get_class($m) . "\n";
        $m->dog('DOG');
        $m->cat('CAT');
        $this->assertTrue(true);
    }

    /**
     * クラス名はnamespace含めてフルで指定しないと、別物になるので注意
     */
    public function test30()
    {
        echo "===".__METHOD__."\n";
        $m = \Mockery::mock('MyHello');  // use App\Libs\MyHello; があっても、これは別物になる
        $m->shouldReceive('cat')->with(\Mockery::on(function ($msg) {
              echo "Hello->cat({$msg}) MOCK\n";
              return true;
          }));

        echo get_class($m) . "\n";
        //$m->dog('DOG');  // このメソッドは存在しないのでエラーになる
        $m->cat('CAT');
        $this->assertTrue(true);
    }
}
```

## phpunit実行結果

```
$ vendor/bin/phpunit 
PHPUnit 7.5.6 by Sebastian Bergmann and contributors.

..===Tests\Unit\MyHelloTest::test10
Hello->__construct(CONSTRACT)
Mockery_0_App_Libs_MyHello
Hello->dog(DOG)
Hello->cat(CAT) MOCK
.===Tests\Unit\MyHelloTest::test11
Hello->__construct(CONSTRACT)
Mockery_0_App_Libs_MyHello
Hello->dog(DOG)
Hello->cat(CAT) MOCK
.===Tests\Unit\MyHelloTest::test12
Hello->__construct(CONSTRACT)
Mockery_0_App_Libs_MyHello
Hello->dog(DOG)
Hello->cat(CAT) MOCK
.===Tests\Unit\MyHelloTest::test20
Hello->__construct(CONSTRACT)
Mockery_1_App_Libs_MyHello_App_Libs_MyHello
Hello->dog(DOG)
Hello->cat(CAT) MOCK
.===Tests\Unit\MyHelloTest::test30
Mockery_2__MyHello
Hello->cat(CAT) MOCK
.                                                             7 / 7 (100%)

Time: 260 ms, Memory: 14.00 MB

OK (7 tests, 13 assertions)
```