phpunit mock method multiple calls with different arguments phpunit mock method multiple calls with different arguments php php

phpunit mock method multiple calls with different arguments


It's not ideal to use at() if you can avoid it because as their docs claim

The $index parameter for the at() matcher refers to the index, starting at zero, in all method invocations for a given mock object. Exercise caution when using this matcher as it can lead to brittle tests which are too closely tied to specific implementation details.

Since 4.1 you can use withConsecutive eg.

$mock->expects($this->exactly(2))     ->method('set')     ->withConsecutive(         [$this->equalTo('foo'), $this->greaterThan(0)],         [$this->equalTo('bar'), $this->greaterThan(0)]       );

If you want to make it return on consecutive calls:

  $mock->method('set')         ->withConsecutive([$argA1, $argA2], [$argB1], [$argC1, $argC2])         ->willReturnOnConsecutiveCalls($retValueA, $retValueB, $retValueC);


The PHPUnit Mocking library (by default) determines whether an expectation matches based solely on the matcher passed to expects parameter and the constraint passed to method. Because of this, two expect calls that only differ in the arguments passed to with will fail because both will match but only one will verify as having the expected behavior. See the reproduction case after the actual working example.


For you problem you need to use ->at() or ->will($this->returnCallback( as outlined in another question on the subject.

Example:

<?phpclass DB {    public function Query($sSql) {        return "";    }}class fooTest extends PHPUnit_Framework_TestCase {    public function testMock() {        $mock = $this->getMock('DB', array('Query'));        $mock            ->expects($this->exactly(2))            ->method('Query')            ->with($this->logicalOr(                 $this->equalTo('select * from roles'),                 $this->equalTo('select * from users')             ))            ->will($this->returnCallback(array($this, 'myCallback')));        var_dump($mock->Query("select * from users"));        var_dump($mock->Query("select * from roles"));    }    public function myCallback($foo) {        return "Called back: $foo";    }}

Reproduces:

phpunit foo.phpPHPUnit 3.5.13 by Sebastian Bergmann.string(32) "Called back: select * from users"string(32) "Called back: select * from roles".Time: 0 seconds, Memory: 4.25MbOK (1 test, 1 assertion)


Reproduce why two ->with() calls don't work:

<?phpclass DB {    public function Query($sSql) {        return "";    }}class fooTest extends PHPUnit_Framework_TestCase {    public function testMock() {        $mock = $this->getMock('DB', array('Query'));        $mock            ->expects($this->once())            ->method('Query')            ->with($this->equalTo('select * from users'))            ->will($this->returnValue(array('fred', 'wilma', 'barney')));        $mock            ->expects($this->once())            ->method('Query')            ->with($this->equalTo('select * from roles'))            ->will($this->returnValue(array('admin', 'user')));        var_dump($mock->Query("select * from users"));        var_dump($mock->Query("select * from roles"));    }}

Results in

 phpunit foo.phpPHPUnit 3.5.13 by Sebastian Bergmann.FTime: 0 seconds, Memory: 4.25MbThere was 1 failure:1) fooTest::testMockFailed asserting that two strings are equal.--- Expected+++ Actual@@ @@-select * from roles+select * from users/home/.../foo.php:27FAILURES!Tests: 1, Assertions: 0, Failures: 1


From what I've found, the best way to solve this problem is by using PHPUnit's value-map functionality.

Example from PHPUnit's documentation:

class SomeClass {    public function doSomething() {}   }class StubTest extends \PHPUnit_Framework_TestCase {    public function testReturnValueMapStub() {        $mock = $this->getMock('SomeClass');        // Create a map of arguments to return values.        $map = array(          array('a', 'b', 'd'),          array('e', 'f', 'h')        );          // Configure the mock.        $mock->expects($this->any())             ->method('doSomething')             ->will($this->returnValueMap($map));        // $mock->doSomething() returns different values depending on        // the provided arguments.        $this->assertEquals('d', $stub->doSomething('a', 'b'));        $this->assertEquals('h', $stub->doSomething('e', 'f'));    }}

This test passes. As you can see:

  • when the function is called with parameters "a" and "b", "d" is returned
  • when the function is called with parameters "e" and "f", "h" is returned

From what I can tell, this feature was introduced in PHPUnit 3.6, so it's "old" enough that it can be safely used on pretty much any development or staging environments and with any continuous integration tool.