Equivalent of SimpleTest “partial mocks” in PHPUnit?

删除回忆录丶 提交于 2019-12-03 09:31:10
Brenton Alker

From reading the linked page, a SimpleTest partial mock seems to be a mock where only some of the methods are overridden. If this is correct, that functionality is handled by a normal PHPUnit mock.

Inside a PHPUnit_Framework_TestCase, you create a mock with

$mock = $this->getMock('Class_To_Mock');

Which creates an mock instance where all methods do nothing and return null. If you want to only override some of the methods, the second parameter to getMock is an array of methods to override.

$mock = $this->getMock('Class_To_Mock', array('insert', 'update'));

will create an mock instance of Class_To_Mock with the insert and update functions removed, ready for their return values to be specified.

This information is in the phpunit docs.

Note, this answer shows more up to date code examples, for PHPUnit versions starting 5.4

PHPUnit_Framework_TestCase::getMock is deprecated since phpunit 5.4. We can use setMethods instead.

setMethods(array $methods) can be called on the Mock Builder object to specify the methods that are to be replaced with a configurable test double. The behavior of the other methods is not changed. If you call setMethods(null), then no methods will be replaced.

https://phpunit.de/manual/current/en/test-doubles.html

$observer = $this->getMockBuilder(Observer::class)
                 ->setMethods(['update'])
                 ->getMock();

Note that the above getMock is PHPUnit_Framework_MockObject_MockBuilder::getMock. (phpunit5.6)

I don't think PHPUnit supports partial mocks for the system under test. If you're trying to isolate methods then I'm sure your implementation works - I've done that too.

However, I try to avoid doing this, for a couple of reasons.

First, it couples your test very tightly to the internal implementation of the class. Do you really care whether a method called doesLongRunningThing was called, or is it more important that the "LongRunningThing" got done?

Second, when I run into this it always makes me wonder whether I've got one class doing the job of two. An extract class refactoring might be in order. The testing becomes much easier if doesLongRunningThing() becomes its own class, even with a single method.

I believe the solution is to inject the services your SUT depends on (http://en.wikipedia.org/wiki/Dependency_injection). This also makes the DoesLongRunningThing implementation more testable.

Without jumping into interfaces, here's what I would do:

class DoesLongRunningThing {
    public function execute() {
        sleep(10);
        return "stuff";
    }
}

class StuffDoer {
    protected $doesLongRunningThing;

    public function setLongRunningThinger(DoesLongRunningThing $obj) {
        $this->doesLongRunningThing = $obj;
    }

    public function doStuff() {
        return $this->doesLongRunningThing->execute();
    }
}

Now it's easy to mock:

class StuffDoerTestCase extends PHPUnit_Framework_TestCase {
    public function testStuffDoer() {
        $dlrtMock = $this->getMock('DoesLongRunningThing');
        $dlrtMock->expects($this->any())->will($this->returnValue("test stuff"));

        $sd = new StuffDoer();
        $sd->setLongRunningThinger($dlrtMock);
        $result = $sd->doStuff();
        $this->assertEquals($result, "test stuff");
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!