在 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));
【问题讨论】:
ParamConverterInterface
有 getClass()
方法吗?
@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, ...)
【讨论】:
谢谢你提到这一点,确实是缺课给我带来了麻烦。 谢谢。有同样的问题。 我在模拟被测类中的require
d 外部库时遇到了这种情况,但不符合自动加载器。
感谢您指出这一点。我使用了一个只有类名的类,它包含在使用中,但事实证明需要提供类的完整命名空间。【参考方案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 服务?