How can I get PHPUnit MockObjects to return different values based on a parameter?

Viewed 99737

I've got a PHPUnit mock object that returns 'return value' no matter what its arguments:

// From inside a test...
$mock = $this->getMock('myObject', 'methodToMock');
$mock->expects($this->any))
     ->method('methodToMock')
     ->will($this->returnValue('return value'));

What I want to be able to do is return a different value based on the arguments passed to the mock method. I've tried something like:

$mock = $this->getMock('myObject', 'methodToMock');

// methodToMock('one')
$mock->expects($this->any))
     ->method('methodToMock')
     ->with($this->equalTo('one'))
     ->will($this->returnValue('method called with argument "one"'));

// methodToMock('two')
$mock->expects($this->any))
     ->method('methodToMock')
     ->with($this->equalTo('two'))
     ->will($this->returnValue('method called with argument "two"'));

But this causes PHPUnit to complain if the mock isn't called with the argument 'two', so I assume that the definition of methodToMock('two') overwrites the definition of the first.

So my question is: Is there any way to get a PHPUnit mock object to return a different value based on its arguments? And if so, how?

11 Answers

Use a callback. e.g. (straight from PHPUnit documentation):

<?php
class StubTest extends PHPUnit_Framework_TestCase
{
    public function testReturnCallbackStub()
    {
        $stub = $this->getMock(
          'SomeClass', array('doSomething')
        );

        $stub->expects($this->any())
             ->method('doSomething')
             ->will($this->returnCallback('callback'));

        // $stub->doSomething() returns callback(...)
    }
}

function callback() {
    $args = func_get_args();
    // ...
}
?>

Do whatever processing you want in the callback() and return the result based on your $args as appropriate.

I had a similar problem (although slightly different... I didn't need different return value based on arguments, but had to test to ensure 2 sets of arguments were being passed to the same function). I stumbled upon using something like this:

$mock = $this->getMock();
$mock->expects($this->at(0))
    ->method('foo')
    ->with(...)
    ->will($this->returnValue(...));

$mock->expects($this->at(1))
    ->method('foo')
    ->with(...)
    ->will($this->returnValue(...));

It's not perfect, since it requires that the order of the 2 calls to foo() is known, but in practice this probably isn't too bad.

You would probably want to do a callback in a OOP fashion:

<?php
class StubTest extends PHPUnit_Framework_TestCase
{
    public function testReturnAction()
    {
        $object = $this->getMock('class_name', array('method_to_mock'));
        $object->expects($this->any())
            ->method('method_to_mock')
            ->will($this->returnCallback(array($this, 'returnTestDataCallback')));

        $object->returnAction('param1');
        // assert what param1 should return here

        $object->returnAction('param2');
        // assert what param2 should return here
    }
    
    public function returnTestDataCallback()
    {
        $args = func_get_args();

        // process $args[0] here and return the data you want to mock
        return 'The parameter was ' . $args[0];
    }
}
?>

Do you mean something like this?

public function TestSomeCondition($condition){
  $mockObj = $this->getMockObject();
  $mockObj->setReturnValue('yourMethod',$condition);
}

I had a similar problem which I couldn't work out as well (there's surprisingly little information about for PHPUnit). In my case, I just made each test separate test - known input and known output. I realised that I didn't need to make a jack-of-all-trades mock object, I only needed a specific one for a specific test, and thus I separated the tests out and can test individual aspects of my code as a separate unit. I'm not sure if this might be applicable to you or not, but that's down to what you need to test.

Related