web-dev-qa-db-ja.com

PHPUnitで特性をモックおよび注入することは可能ですか?

変更できないサードパーティクラスを拡張する必要があります。クラスの依存関係は、ほとんどの部分がコンストラクターを通じて注入されるため、簡単にモック化できます。ただし、クラスはグローバルサービスを直接呼び出すトレイトのメソッドも使用し、テストの分離を破壊します。問題を示すコードサンプルを次に示します。

trait SomeTrait {
  public function irrelevantTraitMethod() {
    throw new \Exception('Some undesirable behavior.');
  }
}

class SomeSystemUnderTest {
  use SomeTrait;
  public function methodToTest($input) {
    $this->irrelevantTraitMethod();
    return $input;
  }
}

class MockWithTraitTest extends \PHPUnit_Framework_TestCase {
  public function testMethodToTest() {
    $sut = new SomeSystemUnderTest();
    $this->assertSame(123, $sut->methodToTest(123)); // Unexpected exception!
  }
}

PHPUnitでこのような状況に対処するにはどうすればよいですか?トレイトをどうにかして模倣して注入できますか?私が見つけた唯一の解決策は、SUT自体をモックし、特性から問題のあるメソッドを無効にし、実際のSUTメソッドはそのままにしておくことです。しかし、それは正しくありません。

7
TravisCarden

誰かがより良い答えをいつか提供することを願っていますが、当面-他の誰かが同じ問題を扱っている場合に備えて-私が質問で言及した回避策は次のとおりです。同じ特性とSUTが与えられた場合、以下のテストはパスします。 SUTの「部分的なモック」を作成し、traitメソッドのみを置き換え、残りはそのままにして行使およびアサートします。

class MockWithTraitTest extends \PHPUnit_Framework_TestCase {
  public function testMethodToTest() {
    /** @var \SomeSystemUnderTest|\PHPUnit_Framework_MockObject_MockObject $sut */
    $sut = $this->getMockBuilder('\SomeSystemUnderTest')
      ->setMethods(array('irrelevantTraitMethod'))
      ->getMock();
    $this->assertSame(123, $sut->methodToTest(123)); // OK.
  }
}

このアプローチは機能しますが、実装との緊密な結合が必要であり、脆弱なテストにつながる恐れがあるため、私はそれが嫌いです。 (たとえば、ロギング用に別のメソッドをトレイトに追加して、SUTで使用するとどうなりますか?すべてのモックを更新するまで、無関係なテストが失敗します。)トレイト全体をモックできるようにしたいと思います。

1
TravisCarden

あなたが望むことをする方法はありません。トレイトは、クラスやパブリックAPIを定義する方法であり、インターフェースや継承と同じです。依存関係をあざけるようにそれを「あざける」ことはできません。

何が機能するか(試してみてください):リフレクションを使用して、特性が定義するすべてのメソッドを動的に「無効」にします。
しかし、これには別の問題があります。実装クラスは、トレイトのメソッドを上書きするか、同じ名前の別のトレイトのメソッドを使用できます。 「ターゲット」特性が定義するすべてのメソッドを単に「無効にする」だけでは、完全にうまくいくわけではありません。
運が良ければ、リフレクションはそれを回避するのにも役立ちます。

0
marstato