PHPUnit stubing类方法声明为;“最后的”;

PHPUnit stubing类方法声明为;“最后的”;,php,unit-testing,mocking,phpunit,Php,Unit Testing,Mocking,Phpunit,我正在为一个类方法编写一个单元测试,该类方法使用mock调用另一个类的方法,只有需要调用的方法被声明为final,因此PHPUnit无法对其进行模拟。我可以采取不同的方法吗 例如: 要嘲笑的类 class Class_To_Mock { final public function needsToBeCalled($options) { ... } } class Class_To_Mock { final public function need

我正在为一个类方法编写一个单元测试,该类方法使用mock调用另一个类的方法,只有需要调用的方法被声明为final,因此PHPUnit无法对其进行模拟。我可以采取不同的方法吗

例如:

要嘲笑的类

class Class_To_Mock
{
    final public function needsToBeCalled($options)
    {
        ...
    }
}
class Class_To_Mock
{
    final public function needsToBeCalled($options)
    {
        ...
    }
}
我的测试用例

class MyTest extends PHPUnit_Framework_TestCase
{
    public function testDoSomething()
    {
        $mock = $this->getMock('Class_To_Mock', array('needsToBeCalled'));
        $mock->expects($this->once())
             ->method('needsToBeCalled')
             ->with($this->equalTo(array('option'));
    }
}
class MyTest extends PHPUnit_Framework_TestCase
{
    public function testDoSomething()
    {
        $classToTest = $this->getMock('Class_To_Be_Tested', array('getClassToMock'));

        $mock = $this->getMock('Class_To_MockMock', array('needsToBeCalled'));

        $classToTest->expects($this->any())
                    ->method('getClassToMock')
                    ->will($this->returnValue($mock));

        $mock->expects($this->once())
             ->method('needsToBeCalled')
             ->with($this->equalTo(array('option'));

        $classToTest->doSomething();
    }
}

编辑:如果使用Mike B提供的解决方案,并且您要模拟的对象有一个执行类型检查的setter/getter(以确保将正确的对象传递到setter),那么您需要模拟您要测试的类上的getter,并让它返回另一个mock

例如:

要嘲笑的类

class Class_To_Mock
{
    final public function needsToBeCalled($options)
    {
        ...
    }
}
class Class_To_Mock
{
    final public function needsToBeCalled($options)
    {
        ...
    }
}
嘲弄

待测类别

class Class_To_Be_Tested
{
    public function setClassToMock(Class_To_Mock $classToMock)
    {
        ...
    }

    public function getClassToMock()
    {
        ...
    }

    public function doSomething()
    {
        $this->getClassToMock()
             ->needsToBeCalled(array('option'));
    }
}
我的测试用例

class MyTest extends PHPUnit_Framework_TestCase
{
    public function testDoSomething()
    {
        $mock = $this->getMock('Class_To_Mock', array('needsToBeCalled'));
        $mock->expects($this->once())
             ->method('needsToBeCalled')
             ->with($this->equalTo(array('option'));
    }
}
class MyTest extends PHPUnit_Framework_TestCase
{
    public function testDoSomething()
    {
        $classToTest = $this->getMock('Class_To_Be_Tested', array('getClassToMock'));

        $mock = $this->getMock('Class_To_MockMock', array('needsToBeCalled'));

        $classToTest->expects($this->any())
                    ->method('getClassToMock')
                    ->will($this->returnValue($mock));

        $mock->expects($this->once())
             ->method('needsToBeCalled')
             ->with($this->equalTo(array('option'));

        $classToTest->doSomething();
    }
}

我认为PHPUnit不支持对final方法进行存根/模拟。在这种情况下,您可能需要创建自己的存根,并进行一些扩展技巧:

class myTestClassMock {
  public function needsToBeCalled() {
    $foo = new Class_To_Mock();
    $result = $foo->needsToBeCalled();
    return array('option');
  }
}
在PHPUnit手册的

限制

请注意,final、private和static方法不能被存根或模拟。它们被PHPUnit的双重测试功能忽略,并保留其原始行为


我今天偶然发现了这个问题。另一种方法是模拟类实现的接口,因为它实现了一个接口,并且您将该接口用作类型提示

例如,考虑到问题,您可以创建一个接口并按如下方式使用它:

interface Interface_To_Mock
{
    function needsToBeCalled($options);
}

class Class_To_Mock implements Interface_To_Mock
{
    final public function needsToBeCalled($options)
    {
        ...
    }

}

class Class_To_Be_Tested
{
    public function setClassToMock(Interface_To_Mock $classToMock)
    {
        ...
    }

    ...
}

class MyTest extends PHPUnit_Framework_TestCase
{
    public function testDoSomething()
    {
        $mock = $this->getMock('Interface_To_Mock', array('needsToBeCalled'));
        ...
    }
}

如果您正在测试的类依赖于对被模拟的类/方法进行类型检查的getter/setter,那么还需要额外的步骤。我把这个添加到原来的文章中。@ RR请考虑把这个作为答案,而不是把这个问题放在一个问题上。我直接回答了最初的问题。如果OP编辑了他的问题,我没有义务继续更新我的答案以匹配这个问题的移动目标。而且,这些内容已经有3年多的历史了,平均每天点击率不到一次。所以最原始的问题和答案没有那么有趣。不要嘲笑最后的方法。。不要引入模拟最终方法的变通方法,您的代码将更干净。@MikeB当然您没有义务做任何事情。我只是建议把这整条线清理干净。我不是PHP专家,所以我没有深入研究细节。如果您的答案仍然有效,则OP问题的解决方案部分应移至另一个答案。@MikeB我同意
不要模仿最终方法。。不要引入模拟最终方法的变通方法,您的代码将更干净
,问题是旧的遗留代码迫使您这样做:'(