【问题标题】:PHPUnit override mock "willReturn()" statementPHPUnit 覆盖模拟“willReturn()”语句
【发布时间】:2021-04-09 03:10:03
【问题描述】:

我在使用 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 个或更多属性要测试,这会导致大量不必要的代码重复,所以我想只编写一次默认的 spy mock,然后仅在必要时修改,但正如您所见当我尝试“重置”方法行为时,它没有按预期工作。

【问题讨论】:

    标签: php mocking phpunit reset spy


    【解决方案1】:

    看起来每次你使用method,它都会创建一个新的匹配器。
    由于它们都将匹配,因此它将始终选择第一个。

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

        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-&gt;bar_value,得到您想要的结果。

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • 为具有许多依赖关系的类或调用许多其他函数的函数编写测试很快就会变得很痛苦,如果您可以控制正在测试的代码,那么重构可能是值得的。
      • 如果我们有几个属性,它可以工作,但我通过 Use Case 持久化一个实体,自然它成为一个大方法,例如,我有 10 个必须持久的属性,这个解决方案仍然重复代码并且使代码可读性复杂化,想象一下每次传递 10 个参数,(也许我们可以做一个工厂的工厂来解决这个问题?)
      猜你喜欢
      • 2017-02-22
      • 2011-07-19
      • 2021-01-07
      • 2022-01-27
      • 2011-07-17
      • 2014-06-07
      • 1970-01-01
      • 2018-06-21
      • 1970-01-01
      相关资源
      最近更新 更多