PHPUnit: how do I mock multiple method calls with multiple arguments?

58,667

Solution 1

In my case the answer turned out to be quite simple:

$this->expects($this->at(0))
    ->method('write')
    ->with(/* first set of params */);
$this->expects($this->at(1))
    ->method('write')
    ->with(/* second set of params */);

The key is to use $this->at(n), with n being the Nth call of the method. I couldn't do anything with any of the logicalOr() variants I tried.

Solution 2

For others who are looking to both match input parameters and provide return values for multiple calls.. this works for me:

    $mock->method('myMockedMethod')
         ->withConsecutive([$argA1, $argA2], [$argB1, $argB2], [$argC1, $argC2])
         ->willReturnOnConsecutiveCalls($retValue1, $retValue2, $retValue3);

Solution 3

Stubbing a method call to return the value from a map

$map = array(
    array('arg1_1', 'arg2_1', 'arg3_1', 'return_1'),
    array('arg1_2', 'arg2_2', 'arg3_2', 'return_2'),
    array('arg1_3', 'arg2_3', 'arg3_3', 'return_3'),
);
$mock->expects($this->exactly(3))
    ->method('MyMockedMethod')
    ->will($this->returnValueMap($map));

Or you can use

$mock->expects($this->exactly(3))
    ->method('MyMockedMethod')
    ->will($this->onConsecutiveCalls('return_1', 'return_2', 'return_3'));

if you don't need to specify input arguments

Solution 4

In case someone finds this without looking at the correspondent section in the phpunit documentation, you can use the withConsecutive method

$mock->expects($this->exactly(3))
     ->method('MyMockedMethod')
     ->withConsecutive(
         [$arg1, $arg2, $arg3....$argNb],
         [arg1b, $arg2b, $arg3b....$argNb],
         [$arg1c, $arg2c, $arg3c....$argNc]
         ...
     );

The only downside of this being that the code MUST call the MyMockedMethod in the order of arguments supplied. I have not yet found a way around this.

Share:
58,667
Thomas
Author by

Thomas

Updated on July 05, 2022

Comments

  • Thomas
    Thomas 5 months

    I am writing a unit test for a method using PHPUnit. The method I am testing makes a call to the same method on the same object 3 times but with different sets of arguments. My question is similar to the questions asked here and here

    The questions asked in the other posts have to do with mocking methods that only take one argument.

    However, my method takes multiple arguments and I need something like this:

    $mock->expects($this->exactly(3))
    ->method('MyMockedMethod')
        ->with(
            $this->logicalOr(
                $this->equalTo($arg1, $arg2, arg3....argNb),
                $this->equalTo($arg1b, $arg2b, arg3b....argNb),
                $this->equalTo($arg1c, $arg2c, arg3c....argNc)
            )
        );
    

    This code doesn't work because equalTo() validates only one argument. Giving it more than one argument throws an exception:

    Argument #2 of PHPUnit_Framework_Constraint_IsEqual::__construct() must be a numeric

    Is there a way to do a logicalOr mocking for a method with more than one argument?

    Thanks in advance.

  • dr Hannibal Lecter over 10 years
    You misunderstood the question, it's not about the return values, it's about different arguments for multiple calls of the method.
  • scriptin
    scriptin over 10 years
    @drHannibalLecter OK, got it. Just confused with()/will()
  • John Flatness
    John Flatness over 10 years
    I think the key here is that with takes a series of constraints (implying an equalTo if just a value is given). You should be able to use logicalOr, but only in the context of each argument separately (which might not be very useful).
  • dr Hannibal Lecter over 10 years
    @JohnFlatness: You are right, I should! :) I am surprised by the fact that something so basic is limited to one argument.
  • Thomas
    Thomas over 10 years
    @drHannibalLecter This works, but the problem here is that it hard codes your test to care about the internal implementation of the test case.
  • Thomas
    Thomas over 10 years
    @drHannibalLecter I will probably mark this as the answer, but I would like to wait and see of there is anyone has a solution with the logicalor implementation.
  • dr Hannibal Lecter over 10 years
    @Thomas: I agree that it's bad, but unless PHPUnit guys do something about it, I think we're stuck with it.. :-\
  • Geoffrey Brier almost 9 years
    @drHannibalLecter It seems the index starts at "1" and not "0".
  • ashnazg
    ashnazg almost 9 years
    @GeoffreyBrier: my usage as tested right now shows that the index begins at 0.
  • SimonSimCity
    SimonSimCity almost 9 years
    Just a note here: The index for $this->at() starts with zero, but increases by every method-call on the mocked object. @GeoffreyBrier, that may've been the reason why it, for you, was the index 1 .... Read here for more info: phpunit.de/manual/current/en/…
  • VuesomeDev
    VuesomeDev over 8 years
    onconsecutive calls is so much better then manually doing at()
  • masterchief about 7 years
    Thank you @SimonSimCity!! Didn't think it'd be EVERY method call, this is so unstable :(
  • Francis Lewis
    Francis Lewis about 7 years
    onConsecutiveCalls was the answer for me just now. Worked awesome; thank you!
  • scipilot
    scipilot over 5 years
    I believe this is what the "map" functions are for e.g. willReturnMap or returnValueMap - like a lookup from params to returns.
  • Rishiraj Purohit
    Rishiraj Purohit almost 2 years
    quick note at matcher is deprecated and will be removed in phpunit 10, cody's answer should be used instead, including withConsecutive