与 SUT 类型相同且是 SUT 的 属性 的 PHPUnit 模拟对象

PHPUnit mocking object that is the same type as the SUT and is a property of the SUT

我有一个名为 TestClass 的 class,它有一个名为 parent 的 属性。 parent 属性 与 TestClass 的类型相同。 下面显示的是我正在尝试测试的 TestClass 的一部分。

class TestClass
{
    /**
     * @param array $parentIds
     * @return ApiLock[]
     */
    protected function getParentLocks(array $parentIds)
    {
        if ($this->getParent()) {
            $id = array_pop($parentIds);
            return $this->getParent()->getLocks($id, $parentIds);
        }

        return [];
    }

    /**
     * @param array $ids
     * @return ApiLock[]
     */
    protected function getLocks($id, array $ids)
    {
        $locks = $this->getParentLocks($ids);

        if ($this->config->getLockName()) {
            $locks[] = new ApiLock($this->config->getLockName() . '.' . $id, $this->config->getLockWait());
        }

        return $locks;
    }

}

方法getLocks调用了getParentLocks方法。为了测试这个 class,我模拟了 TestClass 对象并将其设置为 SUT 的父对象。类似于此。

$apiLock = $this->getMockBuilder(ApiLock::class)
            ->disableOriginalConstructor()
            ->getMock();
        $parent = $this->getMockBuilder(TestClass::class)
            ->disableOriginalConstructor()
            ->getMock();

        $parent->expects($this->any())
            ->method('getLocks')
            ->will($this->returnValue([$apiLock]));

        $testClass->setParent($parent);

但是当我 运行 测试时,parent 对象上的方法 setLocks 不会 return 存根值但实际上会调用 setLocks 父对象上的方法,测试失败。可能是我没有看到明显的东西。 应该调用存根方法而不是父对象上的实际方法。 请帮助我提前谢谢。

问题与父子关系无关

您应该定义要模拟的方法:

    $parent = $this->getMockBuilder(TestClass::class)
        ->setMethods(['setLocks', 'getLocks'])
        ->disableOriginalConstructor()
        ->getMock();

未被模拟的方法将被定期调用,而您的期望将被忽略。您可能正在使用 PHPUnit 4,因为在 PHPUnit 5 中,您会在警告中看到您定义了对未模拟的方法的期望。


更新: 的确,如果不调用setMethods(),所有方法都应该被mock,但这只适用于public 非抽象方法.

这是 PHPUnit_Framework_MockObject_Generator 中自动确定要模拟的方法的代码:

public function getClassMethods($className)
{
    $class   = new ReflectionClass($className);
    $methods = [];
    foreach ($class->getMethods() as $method) {
        if ($method->isPublic() || $method->isAbstract()) {
            $methods[] = $method->getName();
        }
    }
    return $methods;
}

因此您可以模拟受保护的方法(以及会触发 __call() 的不存在的方法),但除了 public 方法之外,您需要明确指定它们。