如何使用PHPUnit测试模拟方法链

时间:2016-10-18 08:27:44

标签: php unit-testing mocking phpunit

我试图模拟一个链(嵌套)方法来返回所需的值,这就是代码:

public function __construct($db)
{
$this->db = $db;
}

public function getResults()
{
return $this->db->getFinder()->find($this->DBTable);
}

我试过这个模拟但它不起作用:

$dbMock = $this->createMock(DB::class);
        $dbMock = $dbMock
            ->expects(self::any())
            ->method('getFinder')
            ->method('find')
            ->with('questions')
            ->will($this->returnValue('7'));

任何解决方案如何解决这样的问题?

谢谢。

3 个答案:

答案 0 :(得分:2)

虽然@BVengerov他的回答肯定会有效,但我建议改变设计。我认为链接模拟不是要走的路,它会损害可读性,更重要的是,简化测试。

我建议你让Finder班成为班上的一员。因此,您现在只需要模拟Finder

class MyClass {

    private $finder;

    public function __construct(Finder $finder) {
        $this->finder = $finder;
    }

    public function getResults() {
        return $this->finder->find($this->DBTable);
    }
}

此更改使得对此函数进行单元测试(和类!)简单

“但我需要班级其他地方的$db变量!”嗯,首先,这可能表明当前类中的一个类正在死亡被提取。保持课堂小而简单。

但是,作为一个快速而肮脏的解决方案,请考虑添加setFinder() setter,以供测试使用。

答案 1 :(得分:1)

链由一个接一个地调用的对象组成。因此,您需要实现一系列模拟。只需以这样的方式模拟方法,即返回模拟对象。

这样的事情应该有效:

$finderMock = $this->createMock(Finder::class);
$finderMock = $finderMock
    ->expects(self::any)
    ->method('find')
    ->with('questions')
    ->will($this->returnValue('7'));

$dbMock = $this->createMock(DB::class);
$dbMock = $dbMock
    ->expects(self::any())
    ->method('getFinder')
    ->will($this->returnValue($finderMock));

详细了解模拟链in this cool blog post

但是,我并没有真正看到测试链中的重点。 IMO最好将测试限制为一次测试1个模块(功能)或2个模块(交互)。

答案 2 :(得分:1)

使用Mocking Demeter Chains And Fluent Interfaces

现在更简单

简单地

$dbMock = $dbMock
        ->expects(self::any())
        ->method('getFinder->find')
        ->with('questions')
        ->will($this->returnValue('7'));

嘲讽文档中的另一个示例

$object->foo()->bar()->zebra()->alpha()->selfDestruct();

,您想让selfDestruct返回10

$mock = \Mockery::mock('CaptainsConsole');
$mock->shouldReceive('foo->bar->zebra->alpha->selfDestruct')->andReturn(10);