PHPUnit call to undefined method `Mock_x_::method()`

18.8k Views Asked by At

I'm trying to create my first phpunit test and find myself needing to stub a method on an IMailer interface.

interface IMailer
{
    public function send($to, $from, $cc, $subject, $body);
    public function sent();
}

    $mailer = $this->getMockBuilder(
        'IMailer',
        array('send', 'sent'))->getMock();

    $mailer->method('send')->willRreturn(0);

However, I keep getting

PHP Fatal error: 
  Call to undefined method Mock_Mailer_13fc0a04::method()
  in ...Test.php on line 16

a var_dump($mailer); results in

class Mock_IMailer_4c3e02a7#215 (1) {
  private $__phpunit_invocationMocker =>
  NULL
}

Working with the expect($this->any()) gives a dito error - it seems that the mocked object does not have any mock functionality...

I'm running phpunit 3.7.28, and php 5.5.9, on an ubuntu box.

How come? How can I fix it?

2

There are 2 best solutions below

3
On

The getMockBuilder function accepts only the className as parameter. The correct way to initialize your mock object methods would be to use setMethods function (see phpunit docs)

   $mailer = $this->getMockBuilder('IMailer')
       ->setMethods(array('send', 'sent'))
       ->getMock();

Additionally you probably want to have some expects definition also when you use your mock object:

   $mailer->expects($this->any())
        ->method('send')
        ->willReturn(0);

EDIT

The above holds true for newer phpunit versions. For phpunit 3.7.28 the mock object usage is a bit different (i.e. the expects seems to be mandatory and willReturn is not yet available). For 3.7.28 version you should modify the second part to:

   $mailer->expects($this->any())
        ->method('send')
        ->will($this->returnValue(0));

I would recommend updating to later phpunit version as it seems to be somewhat difficult to find documentation to this much older releases.

0
On

An alternative solution, for anybody that is still using old versions of PHPUnit, but still wants to be able to call method() directly, is to override the default mock object class template.

Copy MockObject/Generator/mocked_class.tpl.dist, and name the copy mocked_class.tpl. Then, just add the code for the method() method to the template:

public function method()
{
    $any = new PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount;
    $expects = $this->expects($any);
    $args = func_get_args();
    return call_user_func_array(array($expects, 'method'), $args);
}

This will allow you to call $mock->method() directly. However, you need to still use ->will($this->returnValue(0)) instead of ->willReturn(0). To do that, you need to introduce a custom invocation builder and invocation mocker:

class My_MockObject_Builder_InvocationMocker
    extends PHPUnit_Framework_MockObject_Builder_InvocationMocker {

    public function willReturn( $value ) {
        return $this->will( new PHPUnit_Framework_MockObject_Stub_Return( $value ) );
    }
}

class My_MockObject_InvocationMocker
    extends PHPUnit_Framework_MockObject_InvocationMocker {

    public function expects( PHPUnit_Framework_MockObject_Matcher_Invocation $matcher ) {
        return new My_MockObject_Builder_InvocationMocker($this, $matcher);
    }
}

And update your template again, to use My_MockObject_InvocationMocker instead of PHPUnit_Framework_MockObject_InvocationMocker.

The full template would then look like this:

{prologue}{class_declaration}
{
    protected static $staticInvocationMocker;
    protected $invocationMocker;

{clone}{mocked_methods}
    public function expects(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher)
    {
        return $this->__phpunit_getInvocationMocker()->expects($matcher);
    }

    public function method()
    {
        $any = new PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount;
        $expects = $this->expects($any);
        $args = func_get_args();
        return call_user_func_array(array($expects, 'method'), $args );
    }

    public static function staticExpects(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher)
    {
        return self::__phpunit_getStaticInvocationMocker()->expects($matcher);
    }

    public function __phpunit_getInvocationMocker()
    {
        if ($this->invocationMocker === NULL) {
            $this->invocationMocker = new My_MockObject_InvocationMocker;
        }

        return $this->invocationMocker;
    }

    public static function __phpunit_getStaticInvocationMocker()
    {
        if (self::$staticInvocationMocker === NULL) {
            self::$staticInvocationMocker = new My_MockObject_InvocationMocker;
        }

        return self::$staticInvocationMocker;
    }

    public function __phpunit_hasMatchers()
    {
        return self::__phpunit_getStaticInvocationMocker()->hasMatchers() ||
               $this->__phpunit_getInvocationMocker()->hasMatchers();
    }

    public function __phpunit_verify()
    {
        self::__phpunit_getStaticInvocationMocker()->verify();
        $this->__phpunit_getInvocationMocker()->verify();
    }

    public function __phpunit_cleanup()
    {
        self::$staticInvocationMocker = NULL;
        $this->invocationMocker       = NULL;
    }
}{epilogue}