Here is my 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();
}
Here is my test:
...
$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();
}
Test 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.
My question is: I have faced with such things few times, but every time I can't understand what is going on. Why first expectation is proper, and 2nd not proper?
I forgot to say that I faced with such situation few times. Here is what I've noticed. Using xDebug I saw that inside 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 objects has the same cache inside unittest object and inside observer object. If I apply smth on moduleMatcher - it appear in both places
Hash of $unittest->yesNo differ from $observerObject hash. If I set matcher for some method - it applies only inside unittest class!!!
Why did it happen? How to prevent create different objects
When you create object through object manager
$this->observerMock = $objectManager->getObject(
'Observer',
[
'moduleManager' => $this->moduleManagerMock,
'yesNo' => $this->yesNoMock,
]
);
Variables 'moduleManager' and 'yesNo' should be the same as variables in constructor:
public function __construct(Manager $moduleManager, Source\Yesno $yesNo)
{
$this->moduleManager = $moduleManager;
$this->yesNo = $yesNo;
}
Here is code where phpunit check this:
foreach ($method->getParameters() as $parameter) {
$parameterName = $parameter->getName();
$argClassName = null;
$defaultValue = null;
if (array_key_exists($parameterName, $arguments)) {
$constructArguments[$parameterName] = $arguments[$parameterName];
continue;
}
Typically, you should not mock/stub your system under test itself. Since in your test case, the $this->observerMock
object is itself a stub object (which mimicks the interface of another class, but without providing any implementation).
This means that the methods m1
and m2
are also mock methods that will not do anything when they're called. Subsequently, the mocked methods on your dependencies (moduleManagerMock
and yesNoMock
) will never be called and that's why your expectations fail.
To correctly test your desired behaviour, use your Observer
class directly:
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();
}