PHPUnit与Laravel HTTP测试:忽略顺序断言列表(非关联数组)
Laravel HTTP测试中JSON数组的无序断言解决方案
问题场景
在PHPUnit中使用assertEquals()或assertSame()断言数组时,会严格基于键值对进行比较。对于非关联数组(列表),如果元素顺序无关但实际返回顺序与预期不同,这些方法会因为把索引视为键而导致测试失败,通常可以通过自定义断言方法解决:
protected function assertListWithoutOrderEquals(array $expected, array $actual): void { sort($expected); sort($actual); $this->assertEquals($expected, $actual); }
但在Laravel HTTP测试中断言JSON响应时,Laravel会自动将JSON转为数组/集合,同样存在顺序敏感的问题。例如测试genres字段时,仅因返回顺序与预期不一致就导致测试失败:
use Illuminate\Testing\Fluent\AssertableJson; public function test_http_response(): void { $expectedData = [ 'id' => 1, 'name' => 'The fantastic book', 'genres' => ['science-fiction', 'drama', 'mystery'], // 其他字段 ]; $response = $this->get('url'); $response->assertJson( fn(AssertableJson $json) => $json->where('id', $expectedData['id']) ->where('name', $expectedData['name']) ->where('genres', $expectedData['genres']) // 顺序敏感,会导致测试失败 ->etc() ); }
之前尝试过一种繁琐的写法,利用集合的diff()方法做单向检查,同时校验集合长度来确保双向一致:
->where('genres', fn($genres) => $genres->diff($expectedData['genres'])->isEmpty() && $genres->count() == count($expectedData['genres']))
更简洁的解决方案
方案1:使用PHPUnit的assertEqualsCanonicalizing断言
PHPUnit提供了assertEqualsCanonicalizing()方法,会自动忽略数组/集合的元素顺序,仅比较内容是否完全一致。可以直接在闭包中调用该方法:
->where('genres', fn($genres) => $this->assertEqualsCanonicalizing($expectedData['genres'], $genres->all()))
方案2:利用集合排序后比较
通过对预期数组和返回的集合分别排序,再比较排序后的结果,写法直观简洁:
->where('genres', fn($genres) => $genres->sort()->values()->all() === collect($expectedData['genres'])->sort()->values()->all() )
方案3:自定义通用断言方法
在项目的TestCase类中添加一个针对集合的无序断言方法,方便在所有测试中复用:
protected function assertCollectionEqualsIgnoringOrder(array $expected, \Illuminate\Support\Collection $actual): void { $this->assertEqualsCanonicalizing($expected, $actual->all()); }
然后在测试中调用:
->where('genres', fn($genres) => $this->assertCollectionEqualsIgnoringOrder($expectedData['genres'], $genres))
内容的提问来源于stack exchange,提问作者Ali Mohammad Yavari
相关产品推荐
相关产品推荐

