19

I recently updated PHPunit from 5.3 to 5.5 in an IntegrationTestCase of an app that is CakePhp 3.x based. and I don't understand how to update my mock generation scripts.

Originally I created my mock like this:

$stub = $this->getMock('SomeClass', array('execute'));
$stub->method('execute')
     ->will($this->returnValue($this->returnUrl));

After the change to PHPUnit 5.5 this got me the following warning:

PHPUnit_Framework_TestCase::getMock() is deprecated,
use PHPUnit_Framework_TestCase::createMock()
or PHPUnit_Framework_TestCase::getMockBuilder() instead

In order to fix this warning I changed the mock-generation to:

$stub = $this->getMockBuilder('SomeClass', array('execute'))->getMock();
$stub->method('execute')
     ->will($this->returnValue($this->returnUrl));```

Now I get the following error message when running the test:

exception 'PHPUnit_Framework_MockObject_RuntimeException' 
with message 'Trying to configure method "execute" which cannot be
configured because it does not exist, has not been specified, 
is final, or is static'

Anybody know, how to avoid this error? Thank you.

David Albrecht
  • 634
  • 1
  • 4
  • 15

5 Answers5

37

PHPUnit_Framework_TestCase::getMockBuilder() only takes one (1) argument, the class name. The methods to mock are ment to be defined via the returned mock builder objects setMethods() method.

$stub = $this
    ->getMockBuilder('SomeClass')
    ->setMethods(['execute'])
    ->getMock();

See also

ndm
  • 59,784
  • 9
  • 71
  • 110
  • Thanks, that's it. I should have used setMethods instead of trying to pass an array of method-names as a second-parameter in the getMockBuilder()-method. – David Albrecht Sep 21 '16 at 07:12
  • 1
    FYI, setMethods() is deprecated and should be replaced with onlyMethods() – GordonM Aug 04 '21 at 13:07
  • @GordonM That is correct, but CakePHP 3.x by default requires PHPUnit 6.x at the latest, where `onlyMethods()` doesn't exist yet. I'm not sure if 3.x is even compatible with PhpUnit 8+ – ndm Aug 04 '21 at 14:18
20

I will leave this as an answer to myself, when i reach this problem again:

The mocked method may not be private.

scones
  • 3,317
  • 23
  • 34
2

First of all, it's just

$stub = $this->getMockBuilder('SomeClass')->getMock();

Second, error states that method execute does exist in your class SomeClass.

So, check if it really exists and it's public and not final.

If everything's good, check a full classname, if it real and specified with correct namespace.

To avoid stupid errors with classname, it's better to use this syntax:

$stub = $this->getMockBuilder(SomeClass::class)->getMock();

In this case, if SomeClass doesn't exist or namespace is missed, you will get a clear error about it.

Dmitry Malyshenko
  • 3,001
  • 1
  • 12
  • 20
  • Thanks, the problem was that I did not use the setMethods() function (see ndm's answer). – David Albrecht Sep 21 '16 at 07:14
  • It's not required step though. Do you have this method in original class? – Dmitry Malyshenko Sep 21 '16 at 07:16
  • Yes, I have it in my original class and my test expects "execute" to be called (see code example in my question). – David Albrecht Sep 21 '16 at 07:19
  • @DmitryMalyshenko It is required in case the method isn't public. – ndm Sep 21 '16 at 15:09
  • if it's not public, how can tested class use it? – Dmitry Malyshenko Sep 21 '16 at 15:10
  • The same way as any other method that lives in its scope, it can just call it. – ndm Sep 21 '16 at 15:19
  • But it's not a tested class, it's another class, it's dependency. It's not same scope – Dmitry Malyshenko Sep 21 '16 at 15:20
  • It's an extending class, yes, but for protected methods its still the same scope. It wouldn't be for private methods, but those cannot be mocked in the first place. – ndm Sep 21 '16 at 15:37
  • And yes, I know that in an ideal world you'd only test the public API :) – ndm Sep 21 '16 at 15:41
  • Who said it's extending class? As I see use of a mocker: `$dep = new AClass; $b = new BClass($dep)` . When you test class BClass, you need to mock class AClass. But anyway $b cannot use private (or protected, no matter) methods of $dep. – Dmitry Malyshenko Sep 21 '16 at 15:42
  • I said, because that's how mocks work, they extend the original class. Your example has nothing to with either how mocks work, or with the OPs code, so I don't really know what you are trying to say? – ndm Sep 21 '16 at 15:52
  • I am trying to say, that either method exists and it's public, and then `setMethods` not needed, or it doesn't make any sense to make a mocker at all. – Dmitry Malyshenko Sep 21 '16 at 15:54
  • That would only be true if the class would never call any of its public methods itself, or when you'd create expectations for _all_ these public methods, as not restricting the methods via `setMethods()` would cause _all_ public methods to be mocked. You can't really generalize and say it doesn't make sense, because we don't live in this perfect world where everyone encapsulates their public APIs in a way that internals never access it, and even then there may still be valid use cases for mocking a protected method. – ndm Sep 21 '16 at 16:05
  • Anyways, this is a discussion about programming concepts now, and has nothing do with the OPs problem anymore, which is that he needed to use `setMethods()` because obviously the method wasn't public. So I'd say let's stop it as this point :) – ndm Sep 21 '16 at 16:08
2

Addition to upper messages: split mock method declarations

Instead of this:

$mock
    ->method('persist')
       ->with($this->isInstanceOf(Bucket::class))
       ->willReturnSelf()
    ->method('save')
       ->willReturnSelf()
;

Use this:

$mock
    ->method('persist')
        ->willReturnSelf()
;

$mock
   ->method('save')
       ->willReturnSelf()
;
Mehmet Gökalp
  • 314
  • 3
  • 6
1

Perhaps , the method does not exist in the class that you mock .

Yassine CHABLI
  • 3,459
  • 2
  • 23
  • 43