自定义断言

Pest 的断言 API 默认情况下功能强大,但在某些情况下,您可能需要在测试之间重复编写相同的断言。在这种情况下,创建满足您特定需求的自定义断言非常有用。

自定义断言通常在 tests/Pest.php 文件中定义,但您也可以将其组织到单独的 tests/Expectations.php 文件中以提高可维护性。要在 Pest 中创建自定义断言,请将 extend() 方法链接到 expect() 函数,而无需提供任何断言值。

例如,假设您正在测试一个数字实用程序库,并且您需要经常断言数字落在给定范围内。在这种情况下,您可以创建一个名为 toBeWithinRange() 的自定义断言。

1// Pest.php or Expectations.php...
2expect()->extend('toBeWithinRange', function (int $min, int $max) {
3 return $this->toBeGreaterThanOrEqual($min)
4 ->toBeLessThanOrEqual($max);
5});
6 
7// Tests/Unit/ExampleTest.php
8test('numeric ranges', function () {
9 expect(100)->toBeWithinRange(90, 110);
10});

虽然用户通常在他们的自定义断言中使用 Pest 的内置断言,如 toBeWithinRange() 示例所示,但在某些情况下,您可能需要直接访问断言值以执行您自己的自定义断言逻辑。在这种情况下,您可以通过 $this->value 属性访问传递给 expect($value) 的断言值。

1expect()->extend('toBeWithinRange', function (int $min, int $max) {
2 echo $this->value; // 100
3});

当然,您可能希望用户能够使用您的自定义断言将断言链接在一起。为此,请确保您的自定义断言包含 return $this 语句。

1// Pest.php or Expectations.php...
2expect()->extend('toBeWithinRange', function (int $min, int $max) {
3 // Assertions based on `$this->value` and the given arguments...
4 
5 return $this; // Return this, so another expectations can chain this one...
6});
7 
8// Tests/Unit/ExampleTest.php
9test('numeric ranges', function () {
10 expect(100)
11 ->toBeInt()
12 ->toBeWithinRange(90, 110)
13 ->to...
14});

拦截断言

虽然这被认为是一种高级实践,但您可以通过 intercept() 方法用您自己的实现覆盖现有的断言。使用此方法时,如果断言值是指定类型,则将完全替换现有的断言。例如,您可以替换 toBe() 断言以检查两个 Illuminate\Database\Eloquent\Model 类型的对象是否具有相同的 id

1use Illuminate\Database\Eloquent\Model;
2use App\Models\User;
3 
4// tests/Pest.php or tests/Expectations.php
5expect()->intercept('toBe', Model::class, function(Model $expected) {
6 expect($this->value->id)->toBe($expected->id);
7});
8 
9// tests/Feature/ExampleTest.php
10test('models', function () {
11 $userA = User::find(1);
12 $userB = User::find(1);
13 
14 expect($userA)->toBe($userB);
15});

除了将字符串类型作为第二个参数传递给 intercept() 方法之外,您还可以传递一个闭包,该闭包将被调用以确定是否覆盖核心断言。

1expect()->intercept('toBe', fn (mixed $value) => is_string($value), function (string $expected, bool $ignoreCase = false) {
2 if ($ignoreCase) {
3 assertEqualsIgnoringCase($expected, $this->value);
4 } else {
5 assertSame($expected, $this->value);
6 }
7});

管道断言

在某些情况下,您可能希望运行 Pest 的内置断言之一,但在某些条件下包含自定义断言逻辑。在这些情况下,您可以使用 pipe() 方法。例如,如果给定值为 Eloquent 模型,我们可能希望自定义 toBe() 断言的行为。

1use Illuminate\Database\Eloquent\Model;
2use App\Models\User;
3 
4expect()->pipe('toBe', function (Closure $next, mixed $expected) {
5 if ($this->value instanceof Model) {
6 return expect($this->value->id)->toBe($expected->id);
7 }
8 
9 return $next(); // Run to the original, built-in expectation...
10});

如所示,创建自定义断言可以通过消除重复验证测试按预期运行的逻辑来显著简化您的代码。在下一章中,我们将探讨 Pest 提供的其他 CLI 选项:CLI API 参考