简体   繁体   中英

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. Ie, 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?

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.

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.

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());
     }
}

There is a mock builder method called setMethodsExcept() , which (quote):

"can be called on the Mock Builder object to specify the methods that will not be replaced with a configurable test double while replacing all other public methods. This works inverse to onlyMethods()."

Reference: https://phpunit.readthedocs.io/en/9.5/test-doubles.html

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM