8

我正在编写一个 PHPUnit 测试,我需要在其中模拟一些依赖项,但我需要一些方法才能让它仍然像以前一样工作。即,我有:

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

所以我在做这样的事情:

// 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);

一切都很好,除了方法thisOneINeed()被模拟出来,所以我没有运行复杂的代码,我需要它运行runSomeCode()才能正常工作。该代码 thisOneINeed()不调用任何其他方法,但它是正确测试所必需的,并且它不返回固定值,所以我不能只将静态 returnValue() 放在那里。而且AFAIK PHPunit没有像returnValue()“调用父级”这样的方法。据我所知,它有returnCallback()但没有办法告诉它“为父类调用此方法”。

我可以列出所有方法的列表Dependency,从中删除thisOneINeed并在构建模拟时将其传递给setMethods(),但我不喜欢这种方法,看起来很笨拙。

我也可以这样做:

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

然后用于MockDependency构建模拟对象,这也可以,但我不喜欢手动进行模拟。

那么有没有更好的方法来做到这一点?

4

4 回答 4

9

我认为如果您想使用 PHPUnit 的模拟构建器,那么创建一个包含所有方法的数组,删除您需要的方法,并将其传递给 setMethods 正是您需要做的。

我个人发现在很多情况下拥有一个 ReflectionClass 的子类很有用,我可以在需要时添加方法。

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

您还可以使用不同的模拟框架来支持您想要做的事情。例如,Phake有一个thenCallParent方法。我最近开始使用 Phake,因为我需要能够捕获传递给方法的参数。它有据可查,值得一试。

于 2012-06-07T16:14:18.897 回答
1

我需要模拟受保护的方法,这是 Zach 实现反射类的答案的略微修改版本:

$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.
于 2020-03-20T13:01:07.497 回答
0

只是另一个例子,因为setMethods()在 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());
     }
}
于 2022-02-21T10:24:15.423 回答
0

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

于 2021-09-29T06:47:51.520 回答