168

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?

thanksd
  • 54,176
  • 22
  • 157
  • 150
Ben Dowling
  • 17,187
  • 8
  • 87
  • 103

11 Answers11

139

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.

Howard Sandford
  • 1,650
  • 1
  • 11
  • 8
  • 2
    Can you provide a link to the documentation? I can't seem to find it with "the Google" – Kris Erickson Mar 10 '10 at 18:43
  • 6
    Note that you can use a method as a callback by passing an array, e.g. `$this->returnCallback(array('MyClassTest','myCallback'))`. – Patrick Fisher Jul 12 '11 at 20:30
  • 1
    It should also be possible to pass directly a closure to it – Ocramius Jul 24 '12 at 14:25
  • 7
    This should be used only in rare cases. I would suggest using [returnValueMap](http://www.phpunit.de/manual/3.6/en/test-doubles.html) instead as it does not require custom logic to be written in the callback. – Herman J. Radtke III Dec 22 '12 at 04:31
  • function callback has same name as \PHPUnit_Framework_Assert::callback – Zdenek Machek Apr 15 '14 at 10:10
  • 1
    I can't thank you enough. Also, with PHP versions > 5.4, you can use an anonymous function as the callback. `$this->returnCallback(function() { // ... })` – bmorenate Nov 12 '15 at 02:29
  • This answer is being deprecated. `->will($this->returnCallback(...))` should be replaced with `->willReturnCallback(...)` – thelr Aug 21 '23 at 19:41
135

From the latest phpUnit docs: "Sometimes a stubbed method should return different values depending on a predefined list of arguments. You can use returnValueMap() to create a map that associates arguments with corresponding return values."

$mock->expects($this->any())
    ->method('getConfigValue')
    ->will(
        $this->returnValueMap(
            array(
                array('firstparam', 'secondparam', 'retval'),
                array('modes', 'foo', array('Array', 'of', 'modes'))
            )
        )
    );
smottt
  • 3,272
  • 11
  • 37
  • 44
Nikola Ivancevic
  • 1,371
  • 1
  • 8
  • 2
54

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.

Onema
  • 7,331
  • 12
  • 66
  • 102
Adam
  • 565
  • 4
  • 2
  • 1
    The at() matcher has been deprecated. It will be removed in PHPUnit 10. Please refactor your test to not rely on the order in which methods are invoked. – Nicolas Bonnici Dec 06 '21 at 08:20
30

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];
    }
}
?>
ceckoslab
  • 1,189
  • 1
  • 9
  • 19
Francis Lewis
  • 8,872
  • 9
  • 55
  • 65
25

It is not exactly what you ask, but in some cases it can help:

$mock->expects( $this->any() ) )
 ->method( 'methodToMock' )
 ->will( $this->onConsecutiveCalls( 'one', 'two' ) );

onConsecutiveCalls - returns a list of values in the specified order

kaiser
  • 21,817
  • 17
  • 90
  • 110
Prokhor Sednev
  • 668
  • 7
  • 14
13

Pass two level array, where each element is an array of:

  • first are method parameters, and last is return value.

example:

->willReturnMap([
    ['firstArg', 'secondArg', 'returnValue']
])
oligofren
  • 20,744
  • 16
  • 93
  • 180
antonmarin
  • 131
  • 1
  • 5
4

You also can return the argument as follows:

$stub = $this->getMock(
  'SomeClass', array('doSomething')
);

$stub->expects($this->any())
     ->method('doSomething')
     ->will($this->returnArgument(0));

As you can see in the Mocking documentation, the method returnValue($index) allows to return the given argument.

Anthony
  • 2,014
  • 2
  • 19
  • 29
  • I think Stub vs Mock is already confusing topic for many people. You make it even more confusing by doing `$stub = $this->getMock(` – barell Mar 17 '21 at 09:25
0

Do you mean something like this?

public function TestSomeCondition($condition){
  $mockObj = $this->getMockObject();
  $mockObj->setReturnValue('yourMethod',$condition);
}
eddy147
  • 4,853
  • 8
  • 38
  • 57
  • I think that's SimpleTest code, not PHPUnit. But no, it isn't what I want to achieve. Say I had a mock object that returned a word for a given number. My mock method would need to return "one" when called with 1, "two" when called with 2 etc. $ – Ben Dowling Nov 10 '08 at 14:42
0

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.

JamShady
  • 2,914
  • 1
  • 21
  • 22
  • Unfortunately that wouldn't work in my situation. The mock is being passed into a method I'm testing, and the test method calls the mocked method with different arguments. It's interesting to know that you couldn't solve the problem though. It sounds like this could be a PHPUnit limitation. – Ben Dowling Nov 10 '08 at 16:01
-1
$this->BusinessMock = $this->createMock('AppBundle\Entity\Business');

    public function testBusiness()
    {
        /*
            onConcecutiveCalls : Whether you want that the Stub returns differents values when it will be called .
        */
        $this->BusinessMock ->method('getEmployees')
                                ->will($this->onConsecutiveCalls(
                                            $this->returnArgument(0),
                                            $this->returnValue('employee')                                      
                                            )
                                      );
        // first call

        $this->assertInstanceOf( //$this->returnArgument(0),
                'argument',
                $this->BusinessMock->getEmployees()
                );
       // second call


        $this->assertEquals('employee',$this->BusinessMock->getEmployees()) 
      //$this->returnValue('employee'),


    }
Blackbam
  • 17,496
  • 26
  • 97
  • 150
jjoselon
  • 2,641
  • 4
  • 23
  • 37
-2

Try :

->with($this->equalTo('one'),$this->equalTo('two))->will($this->returnValue('return value'));
erenon
  • 18,838
  • 2
  • 61
  • 93
  • This answer does not apply to the original question, but it details a similar problem I had: verify that a certain _set_ of parameters is provided. PHPUnit's with() accepts multiple arguments, one matcher for each parameter. – TaZ May 25 '16 at 10:44