PHPunit 对 Mock 对象的期望未实现
PHPunit expectation on Mock object is not fulfilled
这是我的 class:
public function __construct(Manager $moduleManager, Source\Yesno $yesNo)
{
$this->moduleManager = $moduleManager;
$this->yesNo = $yesNo;
}
public function my1()
{
$this->moduleManager->isOutputEnabled('');
$this->yesNo->toOptionArray();
}
public function my2()
{
$this->moduleManager->isOutputEnabled('');
$this->yesNo->toOptionArray();
}
这是我的测试:
...
$this->observerMock = $this->getMock(
'path\to\Observer',
null,
[$this->moduleManagerMock, $this->yesNoMock],
'',
true
);
...
public function testMy1()
{
$this->moduleManagerMock->expects($this->exactly(2))->method('isOutputEnabled');
$this->yesNoMock->expects($this->exactly(2))->method('toOptionsArray');
$this->observerMock->my1();
$this->observerMock->my2();
}
测试returns:
Expectation failed for method name is equal to
when invoked 2 time(s). Method was expected to be called 2 times,
actually called 0 times.
我的问题是:我遇到过几次这样的事情,但每次都弄不明白是怎么回事。为什么第一个期望值合适,第二个期望值不合适?
更新 1
忘了说我遇到过几次这样的情况。
这是我注意到的。使用 xDebug 我在 test
里面看到了
[this]
[moduleManager] => ModuleManager_Mock_Name_<hash #1>
[yesNo] => YesNo_Mock_Name_<hash #2>
[observerObject] =>
[moduleManager] => ModuleManager_Mock_Name_<hash #1>
[yesNo] => YesNo_Mock_Name_<hash #3> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
moduleManager 对象在 unittest 对象和 inside observer 对象中有相同的缓存。如果我在 moduleMatcher 上应用 smth - 它出现在两个地方
$unittest->yesNo 的哈希与 $observerObject 哈希不同。如果我为某种方法设置了匹配器——它只适用于单元测试class!!!
为什么会这样?如何防止创建不同的对象
找到更新 2!
当您通过对象管理器创建对象时
$this->observerMock = $objectManager->getObject(
'Observer',
[
'moduleManager' => $this->moduleManagerMock,
'yesNo' => $this->yesNoMock,
]
);
变量'moduleManager'和'yesNo'应与构造函数中的变量相同:
public function __construct(Manager $moduleManager, Source\Yesno $yesNo)
{
$this->moduleManager = $moduleManager;
$this->yesNo = $yesNo;
}
这是 phpunit 检查的代码:
foreach ($method->getParameters() as $parameter) {
$parameterName = $parameter->getName();
$argClassName = null;
$defaultValue = null;
if (array_key_exists($parameterName, $arguments)) {
$constructArguments[$parameterName] = $arguments[$parameterName];
continue;
}
通常,您不应该mock/stub您的被测系统本身。因为在您的测试用例中,$this->observerMock
对象本身就是一个存根对象(它模仿另一个 class 的接口,但没有提供任何实现)。
这意味着方法 m1
和 m2
也是 模拟方法,它们在被调用时不会执行任何操作。随后,您的依赖项(moduleManagerMock
和 yesNoMock
)上的模拟方法将 永远不会被调用 ,这就是您的期望失败的原因。
要正确测试您想要的行为,请直接使用您的 Observer
class:
public function setUp() {
$this->moduleManagerMock = $this->getMock(/*...*/);
$this->yesNoMock = $this->getMock(/*...*/);
// Do not generate a mock object of "Observer", but use the class
// under test itself!
$this->observer = new Observer(
$this->moduleManagerMock,
$this->yesNoMock
);
}
public function testM1() {
$this->moduleManagerMock->expects($this->exactly(2))
->method('isOutputEnabled');
$this->yesNoMock->expects($this->exactly(2))
->method('toOptionsArray');
$this->observer->my1();
$this->observer->my2();
}
这是我的 class:
public function __construct(Manager $moduleManager, Source\Yesno $yesNo)
{
$this->moduleManager = $moduleManager;
$this->yesNo = $yesNo;
}
public function my1()
{
$this->moduleManager->isOutputEnabled('');
$this->yesNo->toOptionArray();
}
public function my2()
{
$this->moduleManager->isOutputEnabled('');
$this->yesNo->toOptionArray();
}
这是我的测试:
...
$this->observerMock = $this->getMock(
'path\to\Observer',
null,
[$this->moduleManagerMock, $this->yesNoMock],
'',
true
);
...
public function testMy1()
{
$this->moduleManagerMock->expects($this->exactly(2))->method('isOutputEnabled');
$this->yesNoMock->expects($this->exactly(2))->method('toOptionsArray');
$this->observerMock->my1();
$this->observerMock->my2();
}
测试returns:
Expectation failed for method name is equal to when invoked 2 time(s). Method was expected to be called 2 times, actually called 0 times.
我的问题是:我遇到过几次这样的事情,但每次都弄不明白是怎么回事。为什么第一个期望值合适,第二个期望值不合适?
更新 1
忘了说我遇到过几次这样的情况。 这是我注意到的。使用 xDebug 我在 test
里面看到了[this]
[moduleManager] => ModuleManager_Mock_Name_<hash #1>
[yesNo] => YesNo_Mock_Name_<hash #2>
[observerObject] =>
[moduleManager] => ModuleManager_Mock_Name_<hash #1>
[yesNo] => YesNo_Mock_Name_<hash #3> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
moduleManager 对象在 unittest 对象和 inside observer 对象中有相同的缓存。如果我在 moduleMatcher 上应用 smth - 它出现在两个地方
$unittest->yesNo 的哈希与 $observerObject 哈希不同。如果我为某种方法设置了匹配器——它只适用于单元测试class!!!
为什么会这样?如何防止创建不同的对象
找到更新 2!
当您通过对象管理器创建对象时
$this->observerMock = $objectManager->getObject(
'Observer',
[
'moduleManager' => $this->moduleManagerMock,
'yesNo' => $this->yesNoMock,
]
);
变量'moduleManager'和'yesNo'应与构造函数中的变量相同:
public function __construct(Manager $moduleManager, Source\Yesno $yesNo)
{
$this->moduleManager = $moduleManager;
$this->yesNo = $yesNo;
}
这是 phpunit 检查的代码:
foreach ($method->getParameters() as $parameter) {
$parameterName = $parameter->getName();
$argClassName = null;
$defaultValue = null;
if (array_key_exists($parameterName, $arguments)) {
$constructArguments[$parameterName] = $arguments[$parameterName];
continue;
}
通常,您不应该mock/stub您的被测系统本身。因为在您的测试用例中,$this->observerMock
对象本身就是一个存根对象(它模仿另一个 class 的接口,但没有提供任何实现)。
这意味着方法 m1
和 m2
也是 模拟方法,它们在被调用时不会执行任何操作。随后,您的依赖项(moduleManagerMock
和 yesNoMock
)上的模拟方法将 永远不会被调用 ,这就是您的期望失败的原因。
要正确测试您想要的行为,请直接使用您的 Observer
class:
public function setUp() {
$this->moduleManagerMock = $this->getMock(/*...*/);
$this->yesNoMock = $this->getMock(/*...*/);
// Do not generate a mock object of "Observer", but use the class
// under test itself!
$this->observer = new Observer(
$this->moduleManagerMock,
$this->yesNoMock
);
}
public function testM1() {
$this->moduleManagerMock->expects($this->exactly(2))
->method('isOutputEnabled');
$this->yesNoMock->expects($this->exactly(2))
->method('toOptionsArray');
$this->observer->my1();
$this->observer->my2();
}