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

PhpUnit TestingMockingPhpunit

Php Problem Overview


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?

Php Solutions


Solution 1 - Php

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.

Solution 2 - Php

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'))
            )
        )
    );

Solution 3 - Php

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.

Solution 4 - Php

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];
    }
}
?>

Solution 5 - Php

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

Solution 6 - Php

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']
])

Solution 7 - Php

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.

Solution 8 - Php

Do you mean something like this?

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

Solution 9 - Php

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.

Solution 10 - Php

$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'),


	}

Solution 11 - Php

Try :

->with($this->equalTo('one'),$this->equalTo('two))->will($this->returnValue('return value'));

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBen DowlingView Question on Stackoverflow
Solution 1 - PhpHoward SandfordView Answer on Stackoverflow
Solution 2 - PhpNikola IvancevicView Answer on Stackoverflow
Solution 3 - PhpAdamView Answer on Stackoverflow
Solution 4 - PhpFrancis LewisView Answer on Stackoverflow
Solution 5 - PhpProkhor SednevView Answer on Stackoverflow
Solution 6 - PhpantonmarinView Answer on Stackoverflow
Solution 7 - PhpGabriel Gcia FdezView Answer on Stackoverflow
Solution 8 - Phpeddy147View Answer on Stackoverflow
Solution 9 - PhpJamShadyView Answer on Stackoverflow
Solution 10 - PhpjjoselonView Answer on Stackoverflow
Solution 11 - PhpRob BoltonView Answer on Stackoverflow