PHPUnit: how do I mock multiple method calls with multiple arguments?
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.

Thomas
Updated on July 05, 2022Comments
-
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 yearsYou misunderstood the question, it's not about the return values, it's about different arguments for multiple calls of the method.
-
scriptin over 10 years@drHannibalLecter OK, got it. Just confused
with()
/will()
-
John Flatness over 10 yearsI think the key here is that
with
takes a series of constraints (implying anequalTo
if just a value is given). You should be able to uselogicalOr
, 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 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 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 almost 9 years@GeoffreyBrier: my usage as tested right now shows that the index begins at 0.
-
SimonSimCity almost 9 yearsJust 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 over 8 yearsonconsecutive calls is so much better then manually doing at()
-
masterchief about 7 yearsThank you @SimonSimCity!! Didn't think it'd be EVERY method call, this is so unstable :(
-
Francis Lewis about 7 yearsonConsecutiveCalls was the answer for me just now. Worked awesome; thank you!
-
scipilot over 5 yearsI believe this is what the "map" functions are for e.g. willReturnMap or returnValueMap - like a lookup from params to returns.
-
Rishiraj Purohit almost 2 yearsquick note at matcher is deprecated and will be removed in phpunit 10, cody's answer should be used instead, including withConsecutive