phrozenbyte / phpunit-throwable-asserts

提供各种与Throwable相关的PHPUnit断言。

v1.1.1 2021-02-15 17:40 UTC

This package is auto-updated.

Last update: 2024-09-16 01:42:48 UTC


README

MIT license Code coverage

PHPUnitThrowableAssertions 是一个小的 PHPUnit 扩展,用于断言可调用对象是否抛出特定的异常、错误或Throwable。

这个PHPUnit扩展允许开发者使用更直观的“断言为”方法,在单个断言中测试可调用对象是否抛出异常、错误和其他Throwable。它是PHPUnit内置的 expectException()expectExceptionMessage()expectExceptionCode() 方法的替代品,但功能更强大。

你需要更多PHPUnit约束?查看 PHPUnitArrayAssertions!它引入了各种断言,可以在单个断言中测试PHP数组和类似数组的数组数据。PHPUnit扩展通常用于API测试,用于断言API结果是否符合某些标准——包括其结构和数据。

Daniel Rudolf 制作。 PHPUnitThrowableAssertions 是免费和开源软件,根据 MIT许可证 发布。

目录

  1. 安装
  2. 使用方法
    1. 约束 CallableThrows
    2. 约束 CallableThrowsNot
    3. CallableProxyCachedCallableProxy
    4. PHP错误、警告和通知

安装

PHPUnitThrowableAssertions 可在 Packagist.org 上找到,并可以使用 Composer 进行安装

composer require --dev phrozenbyte/phpunit-throwable-asserts

此PHPUnit扩展最初是为PHPUnit 8编写的,但应与任何后续的PHPUnit版本兼容。如果不兼容,请勿犹豫,在GitHub上打开一个 新问题,或者,更好的是,创建一个包含建议修复的Pull Request。

使用方法

使用 PHPUnitThrowableAssertions 有三种(等效的)方法

所有选项都完全相同。创建新的约束实例对于高级断言很有用,例如与 PHPUnit\Framework\Constraint\LogicalAnd 一起使用。

如果您想向您的可调用对象传递参数,您可能想使用 CallableProxy。如果您想访问可调用对象的返回值或可能抛出的Throwable,请使用 CachedCallableProxy(特别是其 getReturnValue()getThrowable() 方法)。使用 CallableProxy 可以大大改进错误处理。

如上所述,PHPUnitThrowableAssertions 是 PHPUnit 内置的 expectException() 的更强大替代方案。然而,请注意,PHPUnit 内置的 expectExceptionMessage() 匹配子字符串(即 $this->expectExceptionMessage('test') 不仅匹配消息 "test",还匹配 "This is a test"),而 PHPUnitThrowableAssertions 默认通过相等性进行检查(即 $message = 'test' 只匹配消息 "test")。但是,PHPUnitThrowableAssertions 允许您不仅使用字符串,还可以使用任意约束。因此,例如,为了实现子字符串匹配,请传递一个 PHPUnit\Framework\Constraint\StringContains 约束的实例(即 $message = $this->stringContains('test') 也匹配消息 "This is a test")。

约束 CallableThrows

CallableThrows 约束断言一个 Callable 抛出一个特定的 Throwable

该约束调用给定的 Callable(参数 $callable)并捕获与给定基类(参数 $throwableBaseClassName,默认为 Throwable)匹配的任何 Throwable。任何其他 Throwable 都不会被捕获。然后它断言 Throwable 的类(可选参数 $throwableClassName,默认为 Throwable)、消息(可选参数 $throwableMessage,默认为 null)和代码(可选参数 $throwableCode,默认为 null)与预期匹配,否则抛出 ExpectationFailedException。异常消息可以是字符串,需要精确匹配,也可以是任意 Constraint(例如 PHPUnit\Framework\Constraint\StringContains)以匹配异常消息。约束可选地需要类名的精确匹配(可选参数 $throwableExactMatch,默认为 false)。

ThrowableAssertsTrait 特性公开了两个公共方法用于 CallableThrows 约束:使用 ThrowableAssertsTrait::assertCallableThrows() 进行断言,并使用 ThrowableAssertsTrait::callableThrows() 创建 CallableThrows 约束的新实例。

使用方法

// using `PhrozenByte\PHPUnitThrowableAsserts\ThrowableAssertsTrait` trait
ThrowableAssertsTrait::assertCallableThrows(
    callable $callable,                                // the Callable to call
    string $throwableClassName = Throwable::class,     // assert that a Throwable of the given class is thrown
    Constraint|string $throwableMessage = null,        // assert that its message matches the given constraint
    int|string $throwableCode = null,                  // assert that its code matches the given one
    bool $throwableExactMatch = false,                 // whether an exact match of the class name is required
    string $throwableBaseClassName = Throwable::class, // catch all Throwables of the given class
    string $message = ''                               // additional information about the test
);

// using new instance of `PhrozenByte\PHPUnitThrowableAsserts\Constraint\CallableThrows`
new CallableThrows(
    string $className = Throwable::class,
    Constraint|string $message = null,
    int|string $code = null,
    bool $exactMatch = false,
    string $baseClassName = Throwable::class
);

示例

$controller = new BookController();
$bookName = "The Hitchhiker's Guide to the Galaxy";
$bookReleaseDate = '1979-10-12';

$this->assertCallableThrows(
    $this->callableProxy([ $controller, 'create' ], $bookName, $bookReleaseDate),
    BookAlreadyExistsException::class,
    'Unable to create book: Book already exists'
);

调试

$service = new HitchhikersGuideService();
$towel = false;
$answer = 42;

$this->assertCallableThrows(
    static function () use ($service, $towel, $answer) {
        $service->checkAnswer($answer); // throws a OpaqueAnswerException
        $service->checkTowel($towel);   // throws a PanicException (unreachable code)
    },
    PanicException::class,
    'I forgot my towel'
);

// Will fail with the following message:
//
//     Failed asserting that {closure}() throws a PanicException whose message is 'Time to panic'.
//     Encountered invalid OpaqueAnswerException: I do not understand.
//     --- Expected
//     +++ Actual
//     @@ @@
//     -'Time to panic'
//     +'I do not understand'

约束 CallableThrowsNot

CallableThrowsNot 约束断言一个 Callable 不抛出特定的 Throwable。它可以作为 PHPUnit 内置的 expectNotToPerformAssertions() 方法的更具体替代方案。

该约束调用给定的 Callable(参数 $callable)并捕获与给定类(可选参数 $throwableClassName,默认为 Throwable)、消息(可选参数 $throwableMessage,默认为 null)和代码(可选参数 $throwableCode,默认为 null)匹配的任何 Throwable。所有条件都必须匹配,否则将重新抛出 Throwable。异常消息可以是字符串,需要精确匹配,也可以是任意 Constraint(例如 PHPUnit\Framework\Constraint\StringContains)以匹配异常消息。约束可选地需要类名的精确匹配(可选参数 $throwableExactMatch,默认为 false)。

与否定 CallableThrows 约束相同,后者消费所有不匹配的 Throwable 并抛出 ExpectationFailedExceptionCallableThrowsNot 将重新抛出任何不匹配的 Throwable。只有在 Callable 抛出匹配所有给定条件的 Throwable 时才会抛出 ExpectationFailedException

ThrowableAssertsTrait 特性公开了两个公共方法用于 CallableThrowsNot 约束:使用 ThrowableAssertsTrait::assertCallableThrowsNot() 进行断言,并使用 ThrowableAssertsTrait::callableThrowsNot() 创建 CallableThrowsNot 约束的新实例。

使用方法

// using `PhrozenByte\PHPUnitThrowableAsserts\ThrowableAssertsTrait` trait
ThrowableAssertsTrait::assertCallableThrowsNot(
    callable $callable,                            // the Callable to call
    string $throwableClassName = Throwable::class, // assert that no Throwable of the given class is thrown
    Constraint|string $throwableMessage = null,    // catch Throwables matching the given message constraint only
    int|string $throwableCode = null,              // catch Throwables matching the given code only
    bool $throwableExactMatch = false,             // whether only Throwables of the given class are caught
    string $message = ''                           // additional information about the test
);

// using new instance of `PhrozenByte\PHPUnitThrowableAsserts\Constraint\CallableThrowsNot`
new CallableThrowsNot(
    string $className = Throwable::class,
    Constraint|string $message = null,
    int|string $code = null,
    bool $exactMatch = false
);

示例

$controller = CharacterController();
$character = 'Prostetnik Vogon Jeltz';

$this->assertCallableThrowsNot(
    $this->callableProxy([ $controller, 'meet' ], $character),
    VogonWantsToReadPoetException::class
);

调试

$controller = new BookController();
$bookName = "The Hitchhiker's Guide to the Galaxy";
$bookReleaseDate = '1979-10-12';

$this->assertCallableThrowsNot(
    $this->callableProxy([ $controller, 'create' ], $bookName, $bookReleaseDate),
    BookAlreadyExistsException::class
);

// Will fail with the following message:
//
//     Failed asserting that BookController::create() does not throw a BookAlreadyExistsException
//     Encountered invalid BookAlreadyExistsException: Unable to create book: Book already exists

CallableProxyCachedCallableProxy

PHPUnitThrowableAsserts 在调用可调用对象(Callable)时不需要参数,并且由于PHPUnit如何评估值,因此会丢弃可能的返回值。针对此问题的一个解决方案是使用具有变量继承的匿名函数。作为一个简洁的替代方案,PHPUnitThrowableAsserts 提供了CallableProxyCachedCallableProxy 辅助类。

这两个辅助类在其构造函数中接收要调用的可调用对象(参数 $callable)和要传递的参数(任何后续参数,变长参数 $arguments)。此外,它们还实现了PHPUnit的 PHPUnit\Framework\SelfDescribing 接口和 toString() 方法,通过允许 PHPUnitThrowableAsserts 更好地指定调用方法,从而提高了错误处理能力。CachedCallableProxy 还实现了 getReturnValue()getThrowable() 方法。getReturnValue() 返回可调用对象上次调用的缓存返回值,而 getThrowable() 返回可能抛出的 Throwable

ThrowableAssertsTrait 特性公开了两个公共方法来创建 CallableProxyCachedCallableProxy 的实例:使用 ThrowableAssertsTrait::callableProxy() 创建一个新的 CallableProxy 实例,或者使用 ThrowableAssertsTrait::cachedCallableProxy() 创建一个新的 CachedCallableProxy 实例。

使用方法

// create new instance of `PhrozenByte\PHPUnitThrowableAsserts\CallableProxy`
// using the `PhrozenByte\PHPUnitThrowableAsserts\ThrowableAssertsTrait` trait
ThrowableAssertsTrait::callableProxy(
     callable $callable,    // the Callable to invoke
     mixed    ...$arguments // the arguments to pass to the Callable
);

// create new instance of `PhrozenByte\PHPUnitThrowableAsserts\CachedCallableProxy`
// using the `PhrozenByte\PHPUnitThrowableAsserts\ThrowableAssertsTrait` trait
$proxy = ThrowableAssertsTrait::cachedCallableProxy(
     callable $callable,    // the Callable to invoke
     mixed    ...$arguments // the arguments to pass to the Callable
);

// get return value of the Callable (`CachedCallableProxy` only)
$proxy->getReturnValue();

// get a possibly thrown Throwable (`CachedCallableProxy` only)
$proxy->getThrowable();

示例

$computer = new DeepThought();
$question = 'What is the Answer to the Ultimate Question of Life, the Universe, and Everything?';

// using `PhrozenByte\PHPUnitThrowableAsserts\CallableProxy`
// if the assertion fails, `ExpectationFailedException`'s message will point to DeepThought::ask() as source
$askQuestion = $this->cachedCallableProxy([ $computer, 'ask' ], $question);
$this->assertCallableThrowsNot($askQuestion);
$answer = $askQuestion->getReturnValue();

// using anonymous function
// if the assertion fails, `ExpectationFailedException` will just name {closure} as source
$answer = null;
$this->assertCallableThrowsNot(static function () use ($computer, $question, &$answer) {
    // use variable reference to pass the return value
    $answer = $computer->ask($question);
});

PHP错误、警告和通知

PHPUnit 默认将 PHP 错误E_RECOVERABLE_ERROR)、警告(E_WARNINGE_USER_WARNING)、通知(E_NOTICEE_USER_NOTICEE_STRICT)和弃用通知(E_DEPRECATEDE_USER_DEPRECATED)转换为 PHPUnit\Framework\Error\… 异常(分别为 …\Error…\Warning…\Notice…\Deprecated)。这使得您可以使用 PHPUnitThrowableAssertionsassertCallableThrows()assertCallableThrowsNot() 断言来捕获任何PHP错误;只需使用 PHPUnit\Framework\Error\… 中的一个类即可。

请勿将PHP错误与PHP 7.0中引入的 Error 混淆。后者已经是一个 Throwable,并且可以像往常一样捕获。

示例

$this->assertCallableThrows(
    static function () {
        // triggers a E_NOTICE PHP error
        echo $undefinedVariable;
    },
    \PHPUnit\Framework\Error\Notice::class,
    'Undefined variable: undefinedVariable'
);