41

When I'm unit-testing my php code with PHPUnit, I'm trying to figure out the right way to mock an object without mocking any of its methods.

The problem is that if I don't call getMockBuilder()->setMethods(), then all methods on the object will be mocked and I can't call the method I want to test; but if I do call setMethods(), then I need to tell it what method to mock but I don't want to mock any methods at all. But I need to create the mock so I can avoid calling the constructor in my test.

Here's a trivial example of a method I'd want to test:

class Foobar
{
    public function __construct()
    {
        // stuff happens here ...
    }

    public function myMethod($s)
    {
        // I want to test this
        return (strlen($s) > 3);
    }
}

I might test myMethod() with:

$obj = new Foobar();
$this->assertTrue($obj->myMethod('abcd'));

But this would call Foobar's constructor, which I don't want. So instead I'd try:

$obj = $this->getMockBuilder('Foobar')->disableOriginalConstructor()->getMock();
$this->assertTrue($obj->myMethod('abcd'));

But calling getMockBuilder() without using setMethods() will result in all of its methods being mocked and returning null, so my call to myMethod() will return null without touching the code I intend to test.

My workaround so far has been this:

$obj = $this->getMockBuilder('Foobar')->setMethods(array('none'))
    ->disableOriginalConstructor()->getMock();
$this->assertTrue($obj->myMethod('abcd'));

This will mock a method named 'none', which doesn't exist, but PHPUnit doesn't care. It will leave myMethod() unmocked so that I can call it, and it will also let me disable the constructor so that I don't call it. Perfect! Except that it seems like cheating to have to specify a method name that doesn't exist - 'none', or 'blargh', or 'xyzzy'.

What would be the right way to go about doing this?

Mr Peach
  • 1,364
  • 1
  • 12
  • 20
Brian Kendig
  • 2,452
  • 2
  • 26
  • 36

6 Answers6

53

You can pass null to setMethods() to avoid mocking any methods. Passing an empty array will mock all methods. The latter is the default value for the methods as you've found.

That being said, I would say the need to do this might point out a flaw in the design of this class. Should this method be made static or moved to another class? If the method doesn't require a completely-constructed instance, it's a sign to me that it might be a utility method that could be made static.

David Harkness
  • 35,992
  • 10
  • 112
  • 134
  • 4
    Thank you very much! It looks like setMethods(null) is what I want; this causes no methods to be mocked. setMethods(array()), on the other hand, appears to cause all methods to be mocked, same as not using setMethods() at all. I couldn't find this documented anywhere. What do you mean by "empty parents" though? – Brian Kendig Feb 22 '12 at 21:00
  • Also - I am puzzled by your comment. Aside the specifics of the trivial myMethod() example I provided above, why would a method that needs to be tested this way make you feel that the method should be moved to another class or made static? – Brian Kendig Feb 22 '12 at 21:06
  • @BrianKendig - Autocorrect fail on "parents" which should be "parens". :) I just checked the source and there's no default so you must pass in `null` yourself. – David Harkness Feb 23 '12 at 01:10
  • Thank you; your help is much appreciated. I opened https://github.com/sebastianbergmann/phpunit/issues/509 to ask that this functionality be mentioned in the PHPUnit documentation. – Brian Kendig Feb 23 '12 at 14:59
  • And I see you edited your comment - I agree that a method that doesn't require its instance to be constructed might be better off as static; but even if it does require its constructor, I still prefer my unit tests to disable the constructor and set up the object themselves. If a unit tests the constructor *and* the method at the same time, then it's not really a unit test, in my opinion. :-) – Brian Kendig Feb 23 '12 at 15:13
  • I must be the only person on the planet who manually creates mocked objects ;-/ What ever happened to `class TestableFoobar extends Foobar`? – Theodore R. Smith Feb 23 '12 at 17:12
  • @BrianKendig - I used to focus more on unit testing at the method level but have since joined the class-is-the-unit camp. Using TDD has helped as the tests become a great example on how to use the class. If you use a bunch of mocks to skip the constructor--something you'll never do in production code--you lose this. And since your test code has no tests, you want it to be as straight-forward as possible. – David Harkness Feb 23 '12 at 19:12
  • @TheodoreR.Smith - I do this if I need a stub with a simple canned response or that ignores method calls that would normally do something. I use mocks when I want to a) validate the calls being made and b) give specific return values in response. – David Harkness Feb 23 '12 at 19:13
3

Another hacky, but succinct solution is simply to list the magic constructor as one of the mocked methods:

$mock = $this->getMock('MyClass', array('__construct'));
1

In case, any method, e.g. that is called through the entityManager, can be used you might be able to use this:

$this->entityManager
        ->expects($this->any())
        ->method($this->anything())
        ->willReturn($repository);
urlichsanais
  • 141
  • 5
1

For newer versions of PHPUnit, since the setMethods function is deprecated, you can replace setMethods(array('none')) / setMethods(null) with onlyMethods([])

ShaneOH
  • 1,454
  • 1
  • 17
  • 29
0

Or you can just use getMock() directly.

$mock = $this->getMock('MyClass', null, array(), null, false);

Ryan
  • 4,594
  • 1
  • 32
  • 35
-9

This worked for me:

$this->getMock($class, array(), array(), '', false);
Davert
  • 314
  • 1
  • 5