In PHPUnit, how do I indicate different with() on successive calls to a mocked method?
Asked Answered
F

4

55

I want to call my mocked method twice with different expected arguments. This doesn't work because expects($this->once()) will fail on the second call.

$mock->expects($this->once())
     ->method('foo')
     ->with('someValue');

$mock->expects($this->once())
     ->method('foo')
     ->with('anotherValue');

$mock->foo('someValue');
$mock->foo('anotherValue');

I have also tried:

$mock->expects($this->exactly(2))
     ->method('foo')
     ->with('someValue');

But how do I add a with() to match the second call?

Federicofedirko answered 29/4, 2011 at 22:23 Comment(3)
Why do you need to match the arguments? Couldnt you use onConsecutiveCalls() to say "the first time, returns this, second time returns that"? You'd use exactly(2) and onConsecutiveCalls()Pleopod
the same question from the related block.Tophole
Possible duplicate of phpunit mock method multiple calls with different argumentsAnguished
M
70

You need to use at():

$mock->expects($this->at(0))
     ->method('foo')
     ->with('someValue');

$mock->expects($this->at(1))
     ->method('foo')
     ->with('anotherValue');

$mock->foo('someValue');
$mock->foo('anotherValue');

Note that the indexes passed to at() apply across all method calls to the same mock object. If the second method call was to bar() you would not change the argument to at().

Marmolada answered 30/4, 2011 at 0:44 Comment(1)
Conscious that this is an old reply, In the recent versions of PHPUnit, the at() method has been officially deprecated, so withConsecutive() should be used going forward.Scanlon
A
32

Referencing from the answer from a similar question,

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

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.

Anguished answered 18/8, 2017 at 5:56 Comment(1)
Yeah, it's a really stupid solution in phpunit. Everyone is misled by this fact eg addshore.com/2015/08/misled-by-phpunit-at-methodHalt
C
3

withConsecutive is deprecated too.
There is a solution in this post: Replace PHPUnit method `withConsecutive`

Clavicle answered 28/4, 2023 at 10:51 Comment(1)
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From ReviewConservation
T
2

as ->at() and withConsecutive() are deprecated I solved it like this:

in case we have:

$this->service->foo(
    fooFirstParameter: 'firstParamForTheFirstCall',
    fooSecondParameter: 'secondParamForTheFirstCall'
);

$this->service->foo(
    fooFirstParameter: 'firstParamForTheSecondCall',
    fooSecondParameter: 'secondParamForTheSecondCall'
);

then we can mock and expect the parameters and the returns like this:

$withFirstParameter = ['firstParamForTheFirstCall', 'firstParamForTheSecondCall'];
$withSeoncdParameter = ['secondParamForTheFirstCall', 'secondParamForTheSecondCall'];
$willReturn = ['willReturnInTheFirstCall', 'willReturnInTheSecondCall'];
$mock->expects($this->exactly(2))
     ->method('foo')
     ->willReturnCallback(
         function (
             $fooFirstParameter,
             $fooSecondParameter,
         ) use (
             &$withFirstParameter,
             &$withSeoncdParameter
             &$willReturn,
         ): string|null {
             $this->assertEquals(array_shift($withFirstParameter), $fooFirstParameter);
             $this->assertEquals(array_shift($withSeoncdParameter), $fooSecondParameter);
             return array_shift($willReturn);
         }
    );
These answered 11/5, 2024 at 14:39 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.