Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHPUnit: mock all methods except some

I'm writing a PHPUnit test, where I need to mock some dependency, but I need a couple of methods for it still work as before. I.e., I have:

class Dependency {
// some stuff not important for the test
  public function thisOneINeed() {
   /// complex code
  }
// some more stuff
}

So I was doing something like this:

// prepare mock object
$dep = $this->getMockBuilder('Dependency')->disableOriginalConstructor()->getMock();
// mock out some other method that should return fixed value
$dep->expects($this->any())->method("shouldGetTrue")->will($this->returnValue(true));
// run test code, it will use thisOneINeed() and shouldGetTrue()
$result = $testSubject->runSomeCode($dep);
$this->assertEquals($expected, $result);

And everything is fine except method thisOneINeed() is mocked out so I don't get the complex code to run and I need it to run for runSomeCode() to work properly. That code in thisOneINeed() doesn't call any other methods, but it is needed for the proper test and it doesn't return fixed value, so I can't just put static returnValue() there. And AFAIK PHPunit does not have a method like returnValue() that says "call parent". It has returnCallback() but there's no way to tell it "call this method for parent class" as far as I could see.

I could make the list of all methods in Dependency, remove thisOneINeed from it and pass it to setMethods() when constructing the mock, but I don't like that approach, looks kludgy.

I could also do this:

class MockDependency extends Dependency
{
    // do not let the mock kill thisOneINeed function
    final public function thisOneINeed()
    {
        return parent::thisOneINeed();
    }
}

and then use MockDependency to build the mock object, and this works too, but I don't like having to do the mock manually.

So is there a better way to do this?

like image 551
StasM Avatar asked Jun 07 '12 01:06

StasM


3 Answers

I think if you want to use PHPUnit's mock builder, then creating an array of all methods, removing the one you need, and passing it to setMethods is exactly what you'll need to do.

I've personally found it useful in a lot of cases to have a subclass of ReflectionClass that I can add methods to when I need them.

class MyReflectionClass extends ReflectionClass
{
    public function getAllMethodNamesExcept(array $excluded)
    {
        $methods = array_diff(
            get_class_methods($this->name), 
            $excluded
        );
        return $methods;
    }
}

You could also use a different mocking framework that supports what you want to do. For example, Phake has a thenCallParent method. I've started using Phake recently because I needed to be able to capture the parameters that were passed to a method. It is well-documented and worth a try.

like image 142
Zach Avatar answered Nov 03 '22 03:11

Zach


I needed to mock protected methods, a slightly modified version of Zach's answer implementing the ReflectionClass:

$class = new ReflectionClass(\Foo\Bar::class);

// Get just the method names:
$methods = array_map(function($m){return $m->name;}, $class->getMethods());

$methodsToMock = array_diff(
    $methods,
    array("baz", "qux") // don't mock these.
);

$mockObj = $this->getMockBuilder("\Foo\Bar")
    ->setConstructorArgs(array($this->foo))
    ->setMethods($methodsToMock)
    ->getMock();

$mockObj->baz(); // run as normal.
$mockObj->qux(); // run as normal.
$mockObj->foobar(); // mocked.
like image 41
MarkoPee Avatar answered Nov 03 '22 03:11

MarkoPee


Just another example because setMethods() is deprecated in the last version of PHPUnit :

use Tests\TestCase;

class MyTestClass extends TestCase
{
    public function testMyMethodNotMock()
    {
         $myMockMethods = ['methodOne','methodTwo'];
         $myClassMock = $this->getMockBuilder(MyClass::class)
                             ->setConstructorArgs(['args1'])
                             ->onlyMethods($myMockMethods)
                             ->getMock();
         $this->assertTrue($myClassMock->methodNotMock());
     }
}
like image 30
Samuel Pelletier Avatar answered Nov 03 '22 02:11

Samuel Pelletier