net-tools/phpunit-given

PHPUnit 的 ReturnValueMap 和 ReturnCallback 的替代方案。从 Prophecy 迁移的便捷解决方案。

1.0.5 2024-04-29 18:02 UTC

This package is auto-updated.

Last update: 2024-08-29 18:54:27 UTC


README

Packagist Code Quality Code Coverage Downloads

olvlvl/phpunit-given 提供了 PHPUnit 的 ReturnValueMapReturnCallback 的替代方案,以及从 Prophecy 迁移的便捷解决方案。

免责声明

在大多数情况下,可以使用带有 matchReturnCallback 有效地。如果您对这些方法感到满意且不需要额外功能,请勿使用此包。

用法

这是一个简单示例,更多 用例 详见下文。

use olvlvl\Given\GivenTrait;
use PHPUnit\Framework\TestCase;

final class IntegerNameTest extends TestCase
{
    use GivenTrait; // <-- adds the method 'given'

    public function testName(): void
    {
        $mock = $this->createMock(IntegerName::class);
        $mock->method('name')->will($this
            ->given(new Integer(6))->return("six")
            ->given(new Integer(12))->return("twelve")
            ->default()->throw(LogicException::class)
        );

        $this->assertEquals("six", $mock->name(new Integer(6)));
        $this->assertEquals("twelve", $mock->name(new Integer(12)));

        $this->expectException(LogicException::class);
        $mock->name(new Integer(99));
    }
}

安装

composer require olvlvl/phpunit-given

动机

来自 ProphecyC# MoqGolang MockKotlin Mockk 的经验,我们预期以下至少一个示例能正常工作,但实际上它们并不工作。

$mock = $this->createMock(IntegerName::class);
$mock
    ->method('name')
    ->with(new Integer(6))
    ->willReturn("six");
$mock
    ->method('name')
    ->with(new Integer(12))
    ->willReturn("twelve");

// the next line crashes with: Expectation failed
$this->assertEquals("six", $mock->name(new Integer(6)));
$mock = $this->createMock(IntegerName::class);
$mock
    ->method('name')
    ->with(new Integer(6))->willReturn("six");
    // the next line crashes with: Method parameters already configured
    ->with(new Integer(12))->willReturn("twelve");

$this->assertEquals("six", $mock->name(new Integer(6)));

为了返回给定某些参数的值,我们预计会使用 ReturnValueMapReturnCallback。虽然 ReturnValueMap 似乎很简单,但由于它寻找 精确匹配,在参数中包含对象时将失败,除非它们是相同的实例。此外,ReturnValueMap 不支持约束,您可以使用它来忘记做任何复杂的事情。这让我们只剩下 ReturnCallback,它可以与 match 有效地一起使用,但需要在测试中引入逻辑,这是一种 被劝阻的做法

$mock = $this->createMock(IntegerName::class);
$mock->method('name')->willReturnCallback(fn (Integer $int) => match ($int) {
    new Integer(6) => 'six',
    new Integer(12) => 'twelve',
    default => throw new Exception
}));

创建 olvlvl/phpunit-given 的动机是提供一个类似于其他测试框架的替代方案,用于 ReturnValueMapReturnCallback,并允许从 Prophecy 方便地迁移。

一些 PHPUnit 问题,仅供参考

用例

比较对象

ReturnValueMap 由于它在比较参数时使用严格相等性,因此不能与对象一起使用。以下代码会抛出一个 TypeError 异常,因为 ReturnValueMap 找不到匹配项并默认返回一个 null 值。

$mock = $this->createMock(IntegerName::class);
$mock->method('name')->will($this->returnValueMap([
    [ new Integer(6), "six" ],
    [ new Integer(12), "twelve" ],
]));

$mock->name(new Integer(6)); // throws TypeError

olvlvl/phpunit-given 使用 Assert::equalTo() 替换值,并使用约束比较参数。在参数中使用对象不是问题。

$mock = $this->createMock(IntegerName::class);
$mock->method('name')->will($this
    ->given(new Integer(6))->return("six")
    ->given(new Integer(12))->return("twelve")
);

$this->assertEquals("six", $mock->name(new Integer(6)));
$this->assertEquals("twelve", $mock->name(new Integer(12)));

注意:您可以使用 Assert::identicalTo() 来检查相同的实例。

使用约束

我们已经确定,内部使用 Assert::equalTo() 来替换值。您也可以使用约束代替值。

$mock = $this->createMock(IntegerName::class);
$mock->method('name')->will($this
    ->given(Assert::lessThan(new Integer(6)))->return('too small')
    ->given(Assert::greaterThan(new Integer(9)))->return('too big')
    ->default()->return('just right') // `default()` is a shortcut for `given(Assert::anything())`
);

$this->assertEquals("too small", $mock->name(new Integer(5)));
$this->assertEquals("too big", $mock->name(new Integer(10)));
$this->assertEquals("just right", $mock->name(new Integer(6)));
$this->assertEquals("just right", $mock->name(new Integer(9)));

当然,您也可以使用 ReturnCallback,尽管它会为测试添加逻辑。使用您更舒适的方式。

$mock = $this->createMock(IntegerName::class);
$mock->method('name')->willReturnCallback(fn (Integer $int) => match (true) {
    $int < new Integer(6) => 'too small',
    $int > new Integer(9) => 'too big',
    default => 'just right';
}));

从 Prophecy 迁移

olvlvl/phpunit-given 是一个方便的解决方案,用于从 Prophecy 迁移,因为代码非常相似。

$container = $this->prophesize(ContainerInterface::class);
$container->has('serviceA')->willReturn(true);
$container->has('serviceB')->willReturn(false);
$container = $this->createMock(ContainerInterface::class);
$container->method('has')->will($this
    ->given('serviceA')->return(true)
    ->given('serviceB')->return(false)
);

throw()willThrow() 的替代方案,您可以将 return()throw() 进行不匹配。

$container = $this->prophesize(ContainerInterface::class);
$container->get('serviceA')->willReturn($serviceA);
$container->get('serviceB')->willThrow(new LogicException());
$container = $this->createMock(ContainerInterface::class);
$container->method('get')->will($this
    ->given('serviceA')->return($serviceA)
    ->given('serviceB')->throw(LogicException::class)
);

与 Prophecy 不同,olvlvl/phpunit-given 默认不返回 null,而是抛出异常。

$mock = $this->createMock(IntegerName::class);
$mock->method('name')->will($this
    ->given(new Integer(6))->return("six")
    ->given(new Integer(12))->return("twelve")
);

$mock->name(new Integer(13)); // throws an exception
LogicException : Unexpected invocation: Test\olvlvl\Given\Acme\IntegerName::name(Test\olvlvl\Given\Acme\Integer Object (...)): string, didn't match any of the constraints: [ [ is equal to Test\olvlvl\Given\Acme\Integer Object &000000000000000c0000000000000000 (
'value' => 6
) ], [ is equal to Test\olvlvl\Given\Acme\Integer Object &00000000000001af0000000000000000 (
'value' => 12
) ] ]

持续集成

该项目由 GitHub actions 进行持续测试。

Tests Static Analysis Code Style

行为准则

该项目遵循 贡献者行为准则。通过参与此项目和其社区,您应遵守此准则。

贡献

有关详细信息,请参阅 CONTRIBUTING

许可证

olvlvl/phpunit-givenBSD-3-Clause 许可证下发布。