是否有可能使用 PHPUnit 模拟对象来调用一个神奇的 __call() 方法?

Posted

技术标签:

【中文标题】是否有可能使用 PHPUnit 模拟对象来调用一个神奇的 __call() 方法?【英文标题】:Is it possible, using PHPUnit mock objects, to expect a call to a magic __call() method? 【发布时间】:2011-09-06 19:55:52 【问题描述】:

我在测试中有一个模拟对象。真正的对象 PageRepository 使用 __call() 实现了一个神奇的方法,因此如果您调用 $pageRepository->findOneByXXXX($value_of_field_XXXX),它将在数据库中搜索与该参数匹配的记录。

有没有办法模拟那个方法?

真正的方法调用看起来像这样:

$homepage = $pageRepository->findOneBySlug('homepage');

测试看起来像这样:

$mockPageRepository->expects($this->any())
    ->method('findOneBySlug')
    ->will($this->returnValue(new Page()));

但它不起作用——phpUnit 没有发现方法调用。让它看到方法的唯一方法是在 PageRepository 中显式定义方法。

【问题讨论】:

【参考方案1】:

PHPUnit 的getMock() 接受第二个参数,一个包含要模拟的方法名称的数组。 如果您在此数组中包含方法名称,则模拟对象将包含具有该名称的方法,expects() 和朋友将使用该名称。

这甚至适用于未在“真实”类中定义的方法,因此类似以下的方法应该可以解决问题:

$mockPageRepository = $this->getMock('PageRepository', array('findOneBySlug'));

请记住,您必须明确包含任何其他也需要模拟的方法,因为只有数组中命名的方法是为模拟对象定义的。

【讨论】:

请注意,如果您需要通过模拟构建器执行复杂的操作(例如禁用原始构造函数),则必须显式调用 setMethods(),例如$this->getMockBuilder('ClassName')->setMethods(['findById'])->getMock(); 因为 builder 上的 getMock() 不接受任何参数。【参考方案2】:

我找到了更好的方法来做到这一点。仍然不完美,但您不必手动指定所有模拟类方法,只需指定魔术方法:

    $methods = get_class_methods(PageRepository::class);
    $methods[] = 'findOneBySlug';
    $pageRepositoryMock = $this->getMockBuilder(PageRepository::class)
        ->disableOriginalConstructor()
        ->disableOriginalClone()
        ->disableArgumentCloning()
        ->disallowMockingUnknownTypes()
        ->setMethods($methods)
        ->getMock();

【讨论】:

以上是关于是否有可能使用 PHPUnit 模拟对象来调用一个神奇的 __call() 方法?的主要内容,如果未能解决你的问题,请参考以下文章

不能用嘲弄 phpunit 来模拟对象

phpunit 避免模拟的构造函数参数

我可以在PHPUnit中“模拟”时间吗?

PHPUnit “模拟方法不存在。”当使用 $mock->expects($this->at(...))

PHPUnit 模拟对象和方法类型提示

如何在 PHPUnit 中跨多个测试模拟测试 Web 服务?