olvlvl/phpunit-given

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

dev-main 2023-02-05 14:37 UTC

This package is auto-updated.

Last update: 2024-09-05 18:10:49 UTC


README

Packagist Code Quality Code Coverage Downloads

olvlvl/phpunit-given提供对PHPUnitReturnValueMapReturnCallback的替代方案,以及从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 许可下发布。