8

I need to extend a third party class I cannot modify. The class's dependencies are for the most part injected through the constructor, making them easy to mock. However the class also uses methods from a trait that call global services directly, destroying test isolation. Here's a code sample that illustrates the problem:

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!
  }
}

How does one deal with a situation like this in PHPUnit? Can traits be mocked and injected somehow? The only solution I've found is to mock the SUT itself and just nullify the troublesome methods from the trait, leaving the actual SUT methods intact. But that doesn't feel right.

1

2 Answers 2

1

Hopefully someone will provide a better answer at some point, but in the meantime--in case anyone else is dealing with the same issue--here's the workaround I referred to in the question. Given the same trait and SUT, the below test will pass. It creates a "partial mock" of the SUT, replacing only the trait method and leaving the rest intact to exercise and assert on.

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.
  }
}

This approach works, but I dislike it because I'm afraid it requires too tight coupling to the implementation and could lead to fragile tests. (What if I add another method to the trait, say for logging, and use it in the SUT? It will cause irrelevant test failures until I update all my mocks.) I would much prefer to be able to mock the whole trait.

0

There is no way to do what you want. Traits are a way of defining a classes public API, the same way as interfaces and inheritance do. You cannot "mock" that away as if you were mocking a dependency.

What could work (havent tried): use reflection to dynamically "disable" all the methods the trait defines.
However, there is another problem with this: the implementing class can overwrite the methods of the trait or use a method of another trait with the same name. Just simply "disabling" all the methods the "target" trait defines will not entirely do the trick.
If you are lucky, reflection will help you work around that, too.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.