37

I'm trying to migrate a bunch of tests from SimpleTest to PHPUnit and I was wondering if there is an equivalent for SimpleTest's partial mocks.

I can't seem to find anything in the documentation which suggests that this feature is available, but it occurred to me that I could just use a subclass. Is this a good or bad idea?

class StuffDoer {
    protected function doesLongRunningThing() {
        sleep(10);
        return "stuff";
    }
    public function doStuff() {
        return $this->doesLongRunningThing();
    }
}

class StuffDoerTest {
    protected function doesLongRunningThing() {
        return "test stuff";
    }
}

class StuffDoerTestCase extends PHPUnit_Framework_TestCase {
    public function testStuffDoer() {
        $sd = new StuffDoerTest();
        $result = $sd->doStuff();
        $this->assertEquals($result, "test stuff");
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Shabbyrobe
  • 12,298
  • 15
  • 60
  • 87

5 Answers5

56

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

Y. E.
  • 687
  • 1
  • 10
  • 29
Brenton Alker
  • 8,947
  • 3
  • 36
  • 37
23

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)

Nobu
  • 9,965
  • 4
  • 40
  • 47
6

The method setMethods was deprecated. Now it works:

$mock = $this->getMockBuilder(ClassToMock::class)
    ->onlyMethods(['insert', 'update'])
    ->getMock();
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jsowa
  • 9,104
  • 5
  • 56
  • 60
3

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");
    }
}
Ian
  • 977
  • 8
  • 14
  • I think PHPUnit is just a tool, and it doesn't care about system under test (it's a tool user's deal). According to Brenton's answer, PHPUnit _supports_ "partial mocks". Starting after the first sentence your answer is perfect (+1 for it). – Serge S. Jul 13 '15 at 06:25
-1

It could be that the class you are mocking is not in scope (I had this issue). When resolved, I was able to mock one function and test the actual logic of another.

$mockController = $this->getMockBuilder('ControllerClassName')
                       ->setMethods(['functionToMock'])
                       ->getMock();

$result = $mockController->anotherFunction();
$this->assertEquals(true, $result);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Matt Doran
  • 2,050
  • 2
  • 16
  • 18