75

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?

2
  • You have not formulated a question (with real words). Also you have not explained why the code you added specifically does not work for you. That might sound redundant, but that information will help to make your question more clear and easier to provide an answer. We can not look into your brain. Commented Jun 9, 2012 at 15:41
  • @Thomas: I came here to ask this exact question, so I updated and upvoted yours, I hope you don't mind. (And now we play the waiting game...) Commented Jun 9, 2012 at 20:45

4 Answers 4

100

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.

Sign up to request clarification or add additional context in comments.

11 Comments

@drHannibalLecter This works, but the problem here is that it hard codes your test to care about the internal implementation of the test case.
@drHannibalLecter It seems the index starts at "1" and not "0".
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/…
quick note at matcher is deprecated and will be removed in phpunit 10, cody's answer should be used instead, including withConsecutive
@RishirajPurohit We cannot use cody's answer as InvocationMocker::withConsecutive() is deprecated in PHPUnit 9.6 and removed from PHPUnit 10.
|
73

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

1 Comment

As mentioned by chithra on dr-hannibal-lecter's answer, withConsecutive() has also been deprecated in version 9 and removed in version 10. There is no replacement currently... Although, if you are using PHP 8+, some people suggest combining a callback with a match statement for the expected arguments.
34

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

4 Comments

You misunderstood the question, it's not about the return values, it's about different arguments for multiple calls of the method.
@drHannibalLecter OK, got it. Just confused with()/will()
onconsecutive calls is so much better then manually doing at()
onConsecutiveCalls was the answer for me just now. Worked awesome; thank you!
15

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.

1 Comment

I believe this is what the "map" functions are for e.g. willReturnMap or returnValueMap - like a lookup from params to returns.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.