在 PHPUnit 中实现给定接口的模拟对象上的未定义方法?

Posted

技术标签:

【中文标题】在 PHPUnit 中实现给定接口的模拟对象上的未定义方法?【英文标题】:Undefined method on mock object implementing a given interface in PHPUnit? 【发布时间】:2012-09-17 23:23:09 【问题描述】:

我是单元测试和 phpUnit 的新手。

我需要一个模拟,我可以完全控制它,实现ConfigurationInterface 接口。测试对象是ReportEventParamConverter对象,测试必须检查我的对象和接口之间的交互。

ReportEventParamConverter 对象(这里简化):

class ReportEventParamConverter implements ParamConverterInterface

    /**
     * @param Request $request
     * @param ConfigurationInterface $configuration
     */
    function apply(Request $request, ConfigurationInterface $configuration)
    
        $request->attributes->set($configuration->getName(), $reportEvent);
    

    /**
     * @param ConfigurationInterface $configuration
     * @return bool
     */
    function supports(ConfigurationInterface $configuration)
    
        return 'My\Namespaced\Class' === $configuration->getClass();
    

这就是我试图模拟界面的方式:

$cls = 'Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface';
$mock = $this->getMock($mockCls);

我需要模拟两种方法的返回值:getClass()getName()。例如:

$mock->expects($this->any())
    ->method('getClass')
    ->will($this->returnValue('Some\Other\Class'))
;

当我创建一个新的 ReportEventParamConverter 并测试 supports() 方法时,我收到以下 PHPUnit 错误:

致命错误:调用未定义的方法 Mock_ConfigurationInterface_21e9dccf::getClass()。

$converter = new ReportEventParamConverter();
$this->assertFalse($converter->supports($mock));

【问题讨论】:

ParamConverterInterfacegetClass() 方法吗? @hakra 这有关系吗? 请回答问题以向您的问题添加更多信息。那很重要。除此之外,这将是我的第一个假设,为什么 mock 没有该功能。所以一些基本的调试。 @hakra,不,它没有。并且 getClass() 永远不会在任何 ParamConverterInterface 实例上调用。 是在supports()方法中调用的不是吗? 【参考方案1】:

这是因为ConfigurationInterface中没有声明“getClass”方法。此接口中的唯一声明是方法“getAliasName”。

你只需要告诉模拟你将要存根的方法:

$cls = 'Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface';
$mock = $this->getMock($cls, array('getClass', 'getAliasName'));

请注意,没有“getClass”声明,但您也可以存根/模拟不存在的方法。因此你可以模拟它:

$mock->expects($this->any())
    ->method('getClass')
    ->will($this->returnValue('Some\Other\Class'));

但此外,您需要模拟“getAliasName”方法,只要它是接口的方法或抽象方法,并且必须“实现”。例如:

$mock->expects($this->any())
   ->method('getAliasName')
   ->will($this->returnValue('SomeValue'));

【讨论】:

那么如果原始类中不存在方法应该在getMock()调用中声明?如果该方法存在则不需要? 你总是应该告诉你要模拟你要存根/模拟的方法。如果您没有指定,则模拟假定您将对类中的所有方法进行存根(例如,如果您仅指定一个方法 methodA, $mock = $this->getMock('Class', array('methodA') ),模拟假设你只模拟方法A,当你调用另一个方法时:$mock->methodB(),然后“类”中的“真实”方法被执行)【参考方案2】:

Cyprian 的回答帮助了我,但有一个问题需要注意。你可以模拟不存在的类,PHPUnit 不会抱怨。所以你可以这样做

$mock = $this->getMock('SomeClassThatDoesntExistOrIsMisspelledOrPerhapsYouForgotToRequire');

这意味着如果 ConfigurationInterface 在运行时此时不存在,您仍然会收到类似

的消息

致命错误:调用未定义的方法 Mock_ConfigurationInterface_21e9dccf::getClass()。

如果您确定该方法确实存在于该类中,那么可能的问题是该类本身不存在(因为您没有要求它,或者您拼写错误等)。强>


OP 正在使用一个接口。请注意,您必须调用 getMock 而不指定要覆盖的方法列表,或者如果这样做,您必须传递 array(),或传递所有方法名称,否则您将收到如下错误:

PHP 致命错误:类 Mock_HttpRequest_a7aa9ffd 包含 4 个抽象 方法,因此必须声明为抽象或实现 其余方法(HttpRequest::setOption、HttpRequest::execute、 HttpRequest::getInfo, ...)

【讨论】:

谢谢你提到这一点,确实是缺课给我带来了麻烦。 谢谢。有同样的问题。 我在模拟被测类中的 required 外部库时遇到了这种情况,但不符合自动加载器。 感谢您指出这一点。我使用了一个只有类名的类,它包含在使用中,但事实证明需要提供类的完整命名空间。【参考方案3】:

Tyler Collier 的警告是公平的,但不包含关于如何围绕它进行编码的代码 sn-p。请注意,这非常讨厌,您应该修复界面。添加了该警告:

$methods = array_map(function (\ReflectionMethod $m)  return $m->getName();, (new \ReflectionClass($interface))->getMethods());
$methods[] = $missing_method;
$mock = $this->getMock($interface,  $methods);

【讨论】:

以上是关于在 PHPUnit 中实现给定接口的模拟对象上的未定义方法?的主要内容,如果未能解决你的问题,请参考以下文章

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

在 Laravel 中使用 Mockery/phpUnit 时出错

XMPP 上的未读消息计数

在同步对象中实现异步接口

如何在 Java 中实现过滤迭代器?

我可以用 PHPUnit 模拟接口实现吗?