PHPUnit 覆盖 mock "willReturn()" 语句

PHPUnit override mock "willReturn()" statement

我在使用 PHPUnit 模拟时遇到问题,当我第二次调用 method()->willReturn() 时,它似乎保持了在 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);
    }
}

没有在 setUp 上定义,我不得不重写每个测试的默认值,只是为了测试一个这样的方法调用:

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

问题是:有时我有10个或更多的属性要测试,这会导致大量不必要的代码重复,所以我想只写一次默认的间谍模拟,然后只在必要时修改,但正如你可以看到当我尝试“重置”方法行为时,它没有按预期工作。

setUp() 函数在每个 test* 函数之前 运行,也许 setUpBeforeClass() 就是你要找的东西,那只是 运行 一次在该文件中进行测试。

为了减少代码重复,您可以创建一个辅助函数来获取模拟:

public function getFooMock($barReturn = 'bar value', $bazReturn = 'baz value') {
    $mock = $this->createMock(Foo::class);
    $mock->method('getBar')->willReturn($barReturn);
    $mock->method('getBaz')->willReturn($bazReturn);
    return $mock;
}

然后像这样使用它:

$this->sut->execute($this->getFooMock('bar value 0', null));

这应该会产生预期的结果。

看起来每次你使用method,它都会创建一个新的匹配器。
由于所有这些都会匹配,因此它总是 select 第一个。

我在类似情况下的解决方案是使用回调:

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

现在您可以在单个测试中覆盖 $this->bar_value,获得您想要的结果。