Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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->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!

like image 730
rr. Avatar asked Jul 29 '10 22:07

rr.


5 Answers

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"));
    }

}
like image 137
rr. Avatar answered Oct 01 '22 13:10

rr.


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.

like image 32
yvoyer Avatar answered Sep 28 '22 13:09

yvoyer


Try changing $this->at(1) to $this->at(2)

like image 38
Alan Avatar answered Sep 30 '22 13:09

Alan


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");
    })
);
like image 22
Tyler Collier Avatar answered Oct 02 '22 13:10

Tyler Collier


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)
like image 41
edorian Avatar answered Oct 01 '22 13:10

edorian