PHPUnit "Mocked method does not exist." when using $mock->expects($this->at(...))
I've run into a strange issue with PHPUnit mock objects. I have a method that should be called twice, so I'm using the "at" matcher. This works for the first time the method is called, but for some reason, the second time it's called, I get "Mocked method does not exist.". I've used the "at" matcher before and have never run into this.
My code looks something like:
class MyTest extends PHPUnit_Framework_TestCase
{
...
public function testThis()
{
$mock = $this->getMock('MyClass', array('exists', 'another_method', '...'));
$mock->expects($this-&g开发者_如何学Pythont;at(0))
->method('exists')
->with($this->equalTo('foo'))
->will($this->returnValue(true));
$mock->expects($this->at(1))
->method('exists')
->with($this->equalTo('bar'))
->will($this->returnValue(false));
}
...
}
When I run the test, I get:
Expectation failed for method name is equal to <string:exists> when invoked at sequence index 1.
Mocked method does not exist.
If I remove the second matcher, I do not get the error.
Has anyone run into this before?
Thanks!
The issue ended up being with how I understood the "at" matcher to work. Also, my example was not verbatim how it is in my unit test. I thought the "at" matcher counter worked on a per query basis, where it really works on a per object instance basis.
Example:
class MyClass {
public function exists($foo) {
return false;
}
public function find($foo) {
return $foo;
}
}
Incorrect:
class MyTest extends PHPUnit_Framework_TestCase
{
public function testThis()
{
$mock = $this->getMock('MyClass');
$mock->expects($this->at(0))
->method('exists')
->with($this->equalTo('foo'))
->will($this->returnValue(true));
$mock->expects($this->at(0))
->method('find')
->with($this->equalTo('foo'))
->will($this->returnValue('foo'));
$mock->expects($this->at(1))
->method('exists')
->with($this->equalTo('bar'))
->will($this->returnValue(false));
$this->assertTrue($mock->exists("foo"));
$this->assertEquals('foo', $mock->find('foo'));
$this->assertFalse($mock->exists("bar"));
}
}
Correct:
class MyTest extends PHPUnit_Framework_TestCase
{
public function testThis()
{
$mock = $this->getMock('MyClass');
$mock->expects($this->at(0))
->method('exists')
->with($this->equalTo('foo'))
->will($this->returnValue(true));
$mock->expects($this->at(1))
->method('find')
->with($this->equalTo('foo'))
->will($this->returnValue('foo'));
$mock->expects($this->at(2))
->method('exists')
->with($this->equalTo('bar'))
->will($this->returnValue(false));
$this->assertTrue($mock->exists("foo"));
$this->assertEquals('foo', $mock->find('foo'));
$this->assertFalse($mock->exists("bar"));
}
}
FYI, Not sure if its related, but I encountered the same thing, but not with the $this->at()
method, for me it was the $this->never()
method.
This raised the error
$mock->expects($this->never())
->method('exists')
->with('arg');
This fixed the error
$mock->expects($this->never())
->method('exists');
It did the same thing when using the $this->exactly(0)
method.
Hope this help someone.
Try changing $this->at(1)
to $this->at(2)
This is an unfortunate wording of the error message by PHPUnit.
Double check the order of your calls, like @rr's answer mentions.
For me, as far as I know with my own code, I should be using at(0)
and at(1)
respectively, but it wasn't until I used at(2)
and at(3)
instead that it worked. (I'm using session mocking in CakePHP.)
The best way to check the order is to get 'into' the called method and check what's passed. You can do that like this:
$cakePost = $this->getMock('CakePost');
$cakePost->expects($this->once())
->method('post')
->with(
// Add a line like this for each arg passed
$this->callback(function($arg) {
debug("Here's what was passed: $arg");
})
);
As far as i can tell from the Demo code it should work. I produced a working example in case you are running an older PHPUnit Version and want to check that way if it works for you too.
In case that doesn't help maybe you could provide a bit more (at best executable) code ? :)
<?php
class MyTest extends PHPUnit_Framework_TestCase
{
public function testThis()
{
$mock = $this->getMock('MyClass');
$mock->expects($this->at(0))
->method('exists')
->with($this->equalTo('foo'))
->will($this->returnValue(true));
$mock->expects($this->at(1))
->method('exists')
->with($this->equalTo('bar'))
->will($this->returnValue(false));
$this->assertTrue($mock->exists("foo"));
$this->assertFalse($mock->exists("bar"));
}
}
class MyClass {
public function exists($foo) {
return false;
}
}
printing
phpunit MyTest.php
PHPUnit 3.4.15 by Sebastian Bergmann.
.
Time: 0 seconds, Memory: 4.25Mb
OK (1 test, 3 assertions)
Are you sure you included MyClass in your test? I have had some undefined method errors when mocking a class/interface without including it.
May be not when question was raised however today documentation clearly specifies how the at should be used and I quote
Note
The $index parameter for the at() matcher refers to the index, starting at zero, in all method invocations for a given mock object. Exercise caution when using this matcher as it can lead to brittle tests which are too closely tied to specific implementation details.
精彩评论