ささきしき

チラシ

【化石】PHPUnitでtraitのprivateメソッドをテストする(3版;2018-11-06)

片方ずつの話ならググればすぐ出てくるんですけど両方一気にやってるのを見かけなかったので、まぁ組み合わせればいいだけなんですけど備忘録がてら書いておきます。

利用バージョンは以下。

  • PHPUnit 7.3.5
  • PHP 7.1.9
  • Composer/autoload で名前解決してます

setUP()で対応する場合

// MyTrait.php
trait MyTrait
{
  private function privateMethod($arg)
  {
    return true;
  }
}
// traitTest.php
use \PHPUnit\Framework\TestCase;
use MyTrait;

final class MyTreatTest extends TestCase
{
  public function setUp()
  {
    $this->mock  = $this->getMockForTrait(MyTrait::class);
    $this->class = new \ReflectionClass($this->mock);
  }

  public function testPrivateMethod()
  {
    $method = $this->class->getMethod("privateMethod");
    $method->setAccessible(true);

    $argument = "hogefuga"; // optional
    $this->assertTrue($method->invoke($this->mock, $argument));
  }
}

setUpBeforeClass()で対応する場合

trait側は同じなので省略。

// traitTest.php
use \PHPUnit\Framework\TestCase;
use MyTrait;

final class MyTreatTest extends TestCase
{
  protected static $mock;
  protected static $class;

  public static function setUpBeforeClass()
  {
     self::$mock = (new class extends TestCase {})->getMockForTrait(MyTrait::class);
     self::$class = new \ReflectionClass(self::$mock);
  }

  /**
   * @dataProvider dp_PrivateMethod
   */
  public function testPrivateMethod($argument)
  {
    $method = self::$class->getMethod("privateMethod");
    $method->setAccessible(true);

    $this->assertTrue($method->invoke(self::$mock, $argument));
  }

  public function dp_PrivateMethod()
  {
    $mock = $this->getMockForTrait(MyTrait::class);
    $class = new \ReflectionClass(self::$mock);
    $method = $class->getMethod("anotherPrivateMethod");
    $method->setAccessible(true);

    return [
      "argument #1" => $method->invoke($mock, 1),
      "argument #2" => $method->invoke($mock, 2)
    ];
  }
}

メモ

  • たぶんこれで動くと思うんですが、適当に抜き出したので万一動かなかったら大変申し訳ない。言われたら直します。
  • getMockForTrait()
  • ReflectionClass()
  • getMockForTrait()PHPUnitReflectionClass()PHPの標準機能
    • getMockForTrait()は文字通りTraitを受け取ってクラスっぽく展開するメソッド。ただしpublicスコープしか利用できない
    • ReflectionClass()はクラスを受け取ってリバースエンジニアリングできたりするクラス。クラスが持ってるプロパティ・メソッドの一覧を出したり、本件みたいにprivateメソッドを外部から叩いたりできる
  • テストしたいメソッドが1つだけならReflectionMethod()ってクラスのほうが楽そう。というかそっちを紹介する投稿のほうが多い。なんでだろうか
  • setUp()で初期化しないでsetUpBeforeClass()でやりたいものだが、staticメソッドゆえ$thisが生えないので断念
    • 無名クラスを生やしてその場で殴ればselfに代入できる。黒魔術感はある。好みっぽい
    • __construct()でやりたいものだが、なんか失敗した()ので断念
    • テスト件数増えてくるとこの辺所要時間に響くかもしれない。要検討

編集履歴

  • 6 Nov. 2018
    • dataProvider()の仕様についての話題を修正。
  • 23 Sep. 2018
    • setUpBeforeClass()でもできたので追記。
    • dataProvider()での挙動を追記。
  • 21 Sep. 2018
    • 初版

以上