I'm having an issue with PHPUnit mocks, when I call method()->willReturn() on the second time it seems to maintain the first value defined on setUp:
<?php
namespace Test\Application\UseCase;
use Application\UseCase\CreateFoo;
use Application\Exceptions\RequiredValueException;
use Domain\Entity\Foo;
use PHPUnit\Framework\TestCase;
class CreateFooTest extends TestCase
{
private CreateFoo $sut;
private Foo $foo_spy;
public function setUp()
{
$this->foo_spy = $this->createMock(Foo::class);
$this->foo_spy->method('getBar')->willReturn('bar value');
$this->foo_spy->method('getBaz')->willReturn('baz value');
$this->sut = new CreateFoo;
}
public function test_assert_given_foo_without_bar_throws_exception()
{
// Arrange
$this->expectException(RequiredValueException::class);
$this->expectExceptionMessage('Missing Bar value.');
$this->expectExceptionCode(500);
$this->foo_spy->method('getBar')->willReturn(null);
// var_dump($this->foo_spy->getBar());
// outputs: string(bar value)
// expected: null
// Act, Assert
$this->sut->execute($foo_spy);
}
public function test_assert_given_foo_without_baz_throws_exception()
{
// Arrange
$this->expectException(RequiredValueException::class);
$this->expectExceptionMessage('Missing Baz value.');
$this->expectExceptionCode(500);
$this->foo_spy->method('getBaz')->willReturn(null);
// var_dump($this->foo_spy->getBaz());
// outputs: string(baz value)
// expected: null
// Act, Assert
$this->sut->execute($foo_spy);
}
}
Without defining on the setUp
, I have to rewrite every test the default value just to test ONE method call like this:
$this->foo_spy->method('getBar0')->willReturn('bar value 0');
$this->foo_spy->method('getBar1')->willReturn('bar value 1');
$this->foo_spy->method('getBar2')->willReturn('bar value 2');
$this->foo_spy->method('getBar3')->willReturn('bar value 3');
$this->foo_spy->method('getBaz')->willReturn(null);
The problem is: sometimes I have 10 properties or more to test, and it leads to huge unnecessary code duplication, so I would like to write a default spy mock only once and then modify only when necessary, but as you can see when I try to "reset" the method behavior, it doesn't work as expected.
It looks like that every time you use method
, it will create a new matcher.
Since all of them will match, it will always select the first one.
My solution in a similar case was to use a callback:
public function setUp()
{
$this->bar_value = 'bar value';
$this->baz_value = 'baz value';
$this->foo_spy = $this->createMock(Foo::class);
$this->foo_spy->method('getBar')->will( $this->returnCallback(function () { return $this->bar_value; } ) );
$this->foo_spy->method('getBaz')->will( $this->returnCallback(function () { return $this->baz_value; } ) );
$this->sut = new CreateFoo;
}
Now you can override $this->bar_value
in the single tests, getting the result that you want.