phrozenbyte/phpunit-array-asserts

提供各种与数组相关的 PHPUnit 断言,主要用于 API 测试。

v1.2.0 2021-02-27 14:54 UTC

This package is auto-updated.

Last update: 2024-09-14 02:43:13 UTC


README

MIT license Code coverage

PHPUnitArrayAssertions 是一个小的 PHPUnit 扩展,用于改进 PHP 数组和类似数组的测试。它引入了 AssociativeArrayArrayHasKeyWithSequentialArrayArrayHasItemWith 约束。它通常用于 API 测试,以断言 API 结果是否符合某些标准——无论是其结构还是数据。

此 PHPUnit 扩展允许开发者在单个断言中测试结构和数据,使测试用例更加简洁易懂。在某种程度上,它是 PHPUnit 8 中弃用并在 PHPUnit 9 中删除的 ArraySubset 约束的替代品——更强大且更易于理解。有关更多信息,请参阅下面的 “用法”部分“示例”部分

您需要更多 PHPUnit 约束?请查看 PHPUnitThrowableAssertions!它引入了 assertCallableThrows()assertCallableThrowsNot() 断言,以改进对异常和 PHP 错误的测试。它比 PHPUnit 核心中的 expectException() 方法更强大、更灵活。

Daniel Rudolf 用 ❤️ 制作。 PHPUnitArrayAssertions 是免费的开源软件,根据 MIT 许可证 发布。

目录

  1. 安装
  2. 用法
    1. 约束 AssociativeArray
    2. 约束 ArrayHasKeyWith
    3. 约束 SequentialArray
    4. 约束 ArrayHasItemWith
  3. 示例

安装

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

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

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

用法

使用 PHPUnitArrayAssertions 有三种(基本上等效)选项

所有选项都做同样的事情,唯一的区别是静态类和特质都为无效参数抛出 PHPUnit\Framework\InvalidArgumentException 异常。创建新的约束实例对于高级断言很有用,例如与 PHPUnit\Framework\Constraint\LogicalAnd 一起使用。

约束 AssociativeArray

AssociativeArray 约束断言一个值是一个符合给定结构的关联数组,并且该数组的项通过其他约束。

任何本地数组和 ArrayAccess 对象都被视为关联数组,无论它们使用哪个键。但是,数组的项将应用于匹配约束(参数 $constraints)。默认情况下,缺失的项将导致约束失败(参数 $allowMissing,默认为 false)。默认情况下将忽略额外的项(参数 $allowAdditional,默认为 true)。如果您希望存在额外的项时约束失败,将此选项设置为 true,但是请注意,这仅适用于本地数组。预期的键和要应用的约束,以及缺失和/或额外的项是否应该导致约束失败,都通过构造函数传递。约束可以是任意的 Constraint 实例(例如 PHPUnit\Framework\Constraint\StringContains),或者任何静态值,要求值完全匹配。

ArrayAssertsTrait 特性为 AssociativeArray 约束公开了两个公共方法:使用 ArrayAssertsTrait::assertAssociativeArray() 执行断言,并使用 ArrayAssertsTrait::associativeArray() 创建 AssociativeArray 约束的新实例。

用法

// using `\PhrozenByte\PHPUnitArrayAsserts\ArrayAssertsTrait` trait
ArrayAssertsTrait::assertAssociativeArray(
    array $constraints,            // an array with the expected keys and constraints to apply
    array|ArrayAccess $array,      // the associative array to check
    bool $allowMissing = false,    // whether missing items fail the constraint
    bool $allowAdditional = true,  // whether additional items fail the constraint
    string $message = ''           // additional information about the test
);

// using new instance of `\PhrozenByte\PHPUnitArrayAsserts\Constraint\AssociativeArray`
new AssociativeArray(
    array $constraints,
    bool $allowMissing = false,
    bool $allowAdditional = true
);

示例

$data = [
    'id'      => 42,
    'name'    => 'Arthur Dent',
    'options' => [ 'has_towel' => true, 'panic' => false ],
];

// asserts that `$data` is an associative array with exactly the keys:
//     - "id" with a numeric value,
//     - "name" with the value "Arthur Dent", and
//     - "options" with another associative array with the key "panic", whose value must be a boolean
$this->assertAssociativeArray([
    'id'      => $this->isType(IsType::TYPE_INT),
    'name'    => 'Arthur Dent',
    'options' => $this->associativeArray([ 'panic' => $this->isType(IsType::TYPE_BOOL) ], true)
], $data);

调试

$data = [
    'answer' => 21 /* half the truth */
];

$this->assertAssociativeArray([
    'answer' => 42
], $data);

// Will fail with the following message:
//
//     Failed asserting that associative array matches constraints.
//     +----------+-------+----------------------+
//     | Key      | Value | Constraint           |
//     +----------+-------+----------------------+
//     | 'answer' | 21    | Value is equal to 42 |
//     +----------+-------+----------------------+
//     [ ] Allow missing; [x] Allow additional

约束 ArrayHasKeyWith

ArrayHasKeyWith 约束 断言数组具有给定的键,并且其值通过另一个约束。

接受本地数组和 ArrayAccess 对象。如果数组中不存在键(参数 $key),则约束(参数 $constraint)将失败。项目的键和值必须通过的约束通过构造函数传递。约束可以是任意的 Constraint 实例(例如 PHPUnit\Framework\Constraint\StringContains),或者任何静态值,要求值完全匹配。

ArrayAssertsTrait 特性为 ArrayHasKeyWith 约束公开了两个公共方法:使用 ArrayAssertsTrait::assertArrayHasKeyWith() 执行断言,并使用 ArrayAssertsTrait::arrayHasKeyWith() 创建 ArrayHasKeyWith 约束的新实例。

用法

// using `\PhrozenByte\PHPUnitArrayAsserts\ArrayAssertsTrait` trait
ArrayAssertsTrait::assertArrayHasKeyWith(
    string|int $key,              // the key of the item to check
    Constraint|mixed $constraint, // the constraint the item's value is applied to
    array|ArrayAccess $array,     // the array to check
    string $message = ''          // additional information about the test
);

// using new instance of `\PhrozenByte\PHPUnitArrayAsserts\Constraint\ArrayHasKeyWith`
new ArrayHasKeyWith(
    string|int $key,
    Constraint|mixed $constraint
);

示例

$data = [
    'id'      => 42,
    'name'    => 'Arthur Dent',
    'options' => [ 'has_towel' => true, 'panic' => false ],
];

// asserts that $data has the item `name` with the value "Arthur Dent"
$this->assertArrayHasKeyWith('name', 'Arthur Dent', $data);

调试

$data = [];

$this->assertArrayHasKeyWith('answer', 42, $data);

// Will fail with the following message:
//
//     Failed asserting that Array &0 () is an array that
//     has the key 'answer' whose value is equal to 42.

约束 SequentialArray

SequentialArray 约束 断言一个值类似于顺序数组,具有最小和/或最大项数,并且所有项都通过另一个约束。

顺序数组定义为以零开始的递增数字键的有序列表。这对于像 [ "foo", "bar" ] 这样的本地顺序数组尤其如此。空数组也视为有效。预期的最小(参数 $minItems,默认为 0)和/或最大(参数 $maxItems,默认为 null,表示无限)项数,以及应用所有项的约束(可选参数 $constraint),通过构造函数传递。约束可以是任意的 Constraint 实例(例如 PHPUnit\Framework\Constraint\StringContains),或者任何静态值,要求值完全匹配。可以通过将参数 $ignoreKeys 设置为 true(默认为 false)来禁用要求顺序键,这将导致约束仅检查所需项数以及它们是否与给定的约束匹配。

此约束将完全遍历任何给定的 Traversable 对象。它期望 Traversable 是可重置的。对于 NoRewindIterator 实例,它假定迭代器仍处于初始状态。Generator 将被完全耗尽;如果迭代器已经开始,则对象被视为无效。如果给定 Iterator,它将尝试将对象的指针恢复到其之前的状态。对于 NoRewindIterator 实例,这将静默失败。对于具有非唯一键的 Iterator 的行为是未定义的。

ArrayAssertsTrait 特性为 SequentialArray 约束公开了两个方法:使用 ArrayAssertsTrait::assertSequentialArray() 进行断言,以及使用 ArrayAssertsTrait::sequentialArray() 创建一个新的 SequentialArray 约束实例。

用法

// using `\PhrozenByte\PHPUnitArrayAsserts\ArrayAssertsTrait` trait
ArrayAssertsTrait::assertSequentialArray(
    array|Traversable $array,            // the sequential array to check
    int $minItems,                       // required minimum number of items
    int $maxItems = null,                // required maximum number of items (pass null for infinite)
    Constraint|mixed $constraint = null, // optional constraint to apply all items to
    bool $ignoreKeys = false,            // whether to ignore non-sequential keys
    string $message = ''                 // additional information about the test
);

// using new instance of `\PhrozenByte\PHPUnitArrayAsserts\Constraint\SequentialArray`
new SequentialArray(
    int $minItems = 0,
    int $maxItems = null,
    Constraint|mixed $constraint = null,
    bool $ignoreKeys = false
);

示例

$data = [
    "The Hitchhiker's Guide to the Galaxy",
    "The Restaurant at the End of the Universe",
    "Life, the Universe and Everything",
    "So Long, and Thanks for All the Fish",
    "Mostly Harmless",
    "And Another Thing...",
];

// asserts that `$data` is a non-empty sequential array with non-empty items
$this->assertSequentialArray($data, 1, null, $this->logicalNot($this->isEmpty()));

调试

$data = [];

$this->assertSequentialArray($data, 4, null, $this->is(IsType::TYPE_STRING));

// Will fail with the following message:
//
//     Failed asserting that Array &0 () is is a sequential array
//     with ≥ 4 items matching the constraint "is of type "string"".

约束 ArrayHasItemWith

ArrayHasItemWith 约束 断言数组在给定索引处有一个项,并且其值满足另一个约束。

接受本地数组以及 Traversable 对象。如果数组中的项少于所需项数,则约束将失败。要检查的项的索引(参数 $index)以及其值必须满足的约束(参数 $constraint)在构造函数中传递。约束可以是任意的 Constraint 实例(例如 PHPUnit\Framework\Constraint\StringContains),或者任何静态值,需要与值完全匹配。

此约束将完全遍历任何给定的 Traversable 对象。它期望 Traversable 是可重置的。对于 NoRewindIterator 实例,它假定迭代器仍处于初始状态。Generator 将被完全耗尽;如果迭代器已经开始,则对象被视为无效。如果给定 Iterator,它将尝试将对象的指针恢复到其之前的状态。对于 NoRewindIterator 实例,这将静默失败。对于具有非唯一键的 Iterator 的行为是未定义的。

ArrayAssertsTrait 特性为 ArrayHasItemWith 约束公开了两个公共方法:使用 ArrayAssertsTrait::assertArrayHasItemWith() 进行断言,以及使用 ArrayAssertsTrait::arrayHasItemWith() 创建一个新的 ArrayHasItemWith 约束实例。

用法

// using `\PhrozenByte\PHPUnitArrayAsserts\ArrayAssertsTrait` trait
ArrayAssertsTrait::assertArrayHasItemWith(
    int $index,                   // the index of the item to check
    Constraint|mixed $constraint, // the constraint the item's value is applied to
    array|Traversable $array,     // the array to check
    string $message = ''          // additional information about the test
);

// using new instance of `\PhrozenByte\PHPUnitArrayAsserts\Constraint\ArrayHasItemWith`
new ArrayHasItemWith(
    int $index,
    Constraint|mixed $constraint
);

示例

$data = [
    '1979-10-12' => "The Hitchhiker's Guide to the Galaxy",
    '1980-10-00' => "The Restaurant at the End of the Universe",
    '1982-08-00' => "Life, the Universe and Everything",
    '1984-11-09' => "So Long, and Thanks for All the Fish",
    '1992-00-00' => "Mostly Harmless",
    '2009-10-12' => "And Another Thing...",
];

// asserts that `$data` contains "Life, the Universe and Everything" as third item (i.e. at index 2)
$this->assertArrayHasItemWith(2, "Life, the Universe and Everything");

调试

$data = [];

$this->assertArrayHasItemWith(2, 'Arthur Dent', $data);

// Will fail with the following message:
//
//     Failed asserting that Array &0 () is an array that
//     has a value at index 2 which is equal to 'Arthur Dent'.

示例

以下是一个(或多或少)真实的 PHPUnitArrayAssertions 示例。查看 testWithPHPUnitArrayAsserts() 方法,了解如何测试复杂的 API 响应。要比较仅使用 PHPUnit 核心功能的实现,请查看 testWithoutPHPUnitArrayAsserts() 方法。没有 PHPUnitArrayAssertions,您将得到 17 行相当重复的代码,使用这个 PHPUnit 扩展,您可以用 7 行易于理解的代码来测试响应。

<?php
declare(strict_types=1);

namespace YourName\YourProject\Tests;

use PHPUnit\Framework\Constraint\IsType;
use PHPUnit\Framework\TestCase;
use PhrozenByte\PHPUnitArrayAsserts\ArrayAssertsTrait;

class MyTest extends TestCase
{
    use ArrayAssertsTrait;

    public function testWithPHPUnitArrayAsserts(): void
    {
        // 7 lines of easy to understand code to check the API response *with* PHPUnitArrayAsserts

        // implement your test, the result is stored in $responseData

        $responseData = [
            'users' => [
                [
                    'id'      => 42,
                    'name'    => 'Arthur Dent',
                    'options' => [ 'has_towel' => true, 'panic' => false ],
                ],
            ]
        ];

        $this->assertArrayHasKeyWith('users', $this->sequentialArray(1), $responseData);

        $this->assertAssociativeArray([
            'id'      => $this->isType(IsType::TYPE_INT),
            'name'    => 'Arthur Dent',
            'options' => $this->associativeArray([ 'panic' => $this->isType(IsType::TYPE_BOOL) ])
        ], $responseData['users'][0]);
    }
    
    public function testWithoutPHPUnitArrayAsserts(): void
    {
        // 17 lines of pretty repetitive code to check the API response *without* PHPUnitArrayAsserts

        // implement your test, the result is stored in $responseData

        $responseData = [
            'users' => [
                [
                    'id'      => 42,
                    'name'    => 'Arthur Dent',
                    'options' => [ 'has_towel' => true, 'panic' => false ],
                ],
            ]
        ];

        $this->assertArrayHasKey('users', $responseData);
        $this->assertIsArray($responseData['users']);
        $this->assertGreaterThanOrEqual(1, count($responseData['users'])); // won't work for Traversable

        $userData = $responseData['users'][0]; // we can't really rely on the existence of key "0" here :/

        $this->assertArrayHasKey('id', $userData);
        $this->assertIsInt($userData['id']);

        $this->assertArrayHasKey('name', $userData);
        $this->assertSame('Arthur Dent', $userData['name']);

        $this->assertArrayHasKey('options', $userData);
        $this->assertIsArray($userData['options']);

        $this->assertArrayHasKey('panic', $userData['options']);
        $this->assertIsBool($userData['options']['panic']);
    }
}