Undefined method on mock object implementing a given interface in PHPUnit?

22,101

Solution 1

It's because there is no declaration of "getClass" method in ConfigurationInterface. The only declaration in this interface is method "getAliasName".

All you need is to tell the mock what methods you will be stubing:

$cls = 'Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface';
$mock = $this->getMock($cls, array('getClass', 'getAliasName'));

Notice that there is no "getClass" declaration but you can stub/mock non existing method as well. Therefor you can mock it:

$mock->expects($this->any())
    ->method('getClass')
    ->will($this->returnValue('Some\Other\Class'));

But in addtion you need to mock "getAliasName" method as well as long as it's interface's method or abstract one and it has to be "implemented". Eg.:

$mock->expects($this->any())
   ->method('getAliasName')
   ->will($this->returnValue('SomeValue'));

Solution 2

Cyprian's answer helped me, but there's a gotcha to be aware of. You can mock classes that don't exist, and PHPUnit won't complain. So you could do

$mock = $this->getMock('SomeClassThatDoesntExistOrIsMisspelledOrPerhapsYouForgotToRequire');

This means that if ConfigurationInterface doesn't exist at that point during runtime, you'll still get a message like

Fatal error: Call to undefined method Mock_ConfigurationInterface_21e9dccf::getClass().

If you're sure the method really exists on the class, then the likely problem is the class itself doesn't exist (because you haven't required it, or you misspelled it, etc).


The OP is using an interface. Be advised that you must call getMock without specifying the list of methods to override, or if you do, you must either pass array(), or pass ALL the method names, or you'll get an error like the following:

PHP Fatal error: Class Mock_HttpRequest_a7aa9ffd contains 4 abstract methods and must therefore be declared abstract or implement the remaining methods (HttpRequest::setOption, HttpRequest::execute, HttpRequest::getInfo, ...)

Solution 3

Tyler Collier's warning is fair but doesn't contain a code snippet on how to code around it. Note this is very nasty and you should fix the interface instead. With that warning added:

$methods = array_map(function (\ReflectionMethod $m) { return $m->getName();}, (new \ReflectionClass($interface))->getMethods());
$methods[] = $missing_method;
$mock = $this->getMock($interface,  $methods);
Share:
22,101
gremo
Author by

gremo

Updated on January 28, 2020

Comments

  • gremo
    gremo over 4 years

    I'm new to unit testing and PHPUnit.

    I need a mock, on which I have a full control, implementing ConfigurationInterface interface. Test subject is ReportEventParamConverter object and test must check the interaction between my object and the interface.

    ReportEventParamConverter object (here simplified):

    class ReportEventParamConverter implements ParamConverterInterface
    {
        /**
         * @param Request $request
         * @param ConfigurationInterface $configuration
         */
        function apply(Request $request, ConfigurationInterface $configuration)
        {
            $request->attributes->set($configuration->getName(), $reportEvent);
        }
    
        /**
         * @param ConfigurationInterface $configuration
         * @return bool
         */
        function supports(ConfigurationInterface $configuration)
        {
            return 'My\Namespaced\Class' === $configuration->getClass();
        }
    }
    

    And this is the way I'm trying to mock the interface:

    $cls = 'Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface';
    $mock = $this->getMock($mockCls);
    

    I need to simulate the returned values for two methods: getClass() and getName(). For example:

    $mock->expects($this->any())
        ->method('getClass')
        ->will($this->returnValue('Some\Other\Class'))
    ;
    

    When i create a new ReportEventParamConverter and test supports() method, i get the following PHPUnit error:

    Fatal error: Call to undefined method Mock_ConfigurationInterface_21e9dccf::getClass().

    $converter = new ReportEventParamConverter();
    $this->assertFalse($converter->supports($mock));
    
  • gremo
    gremo over 11 years
    So if methods doesn't exist in the original class should be declared in getMock() call? If the method exists then it's not necessary?
  • Cyprian
    Cyprian over 11 years
    You always should tell to mock which methods you are going to stub/mock. If you don't specify that, the mock assume that you will stub all methods from the class (eg. if you specify just one method methodA, $mock = $this->getMock('Class', array('methodA')), the mock assume that you mock ONLY methodA, and when you call another method: $mock->methodB(), then "real" method from "Class" is executed)
  • armin MlkPr
    armin MlkPr almost 9 years
    Thank you for mentioning this, it was indeed a missing class that was causing me problems.
  • Mantas
    Mantas over 8 years
    Thanks. Had the same problem.
  • Curtis Mattoon
    Curtis Mattoon about 8 years
    I experienced this while mocking an external library that was required in the class under test, but not conforming to the autoloader.
  • zhihong
    zhihong almost 6 years
    Thanks for point this out. I used a class with only class name which included in the use, but it turns out needs to provide the full namespace of the class.