You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel接口测试:如何断言响应返回预期分页数据?

问题描述

我有如下Laravel路由:

Route::get('/records', function(Request $request) {
    $page = (int) $request->get('page');
    $limit = (int) $request->get('limit');

    return new JsonResponse(\App\Model\Record::paginate($limit, ['*'], '', $page), 200);
});

同时编写了如下单元测试:

use App\Model\Record;
use Tests\TestCase;

class RecordsEndpointTest extends TestCase
{
    public function testRecordsFetched()
    {
        $response = $this->get('/records?page=1&limit=10');
        $response->assertStatus(200);

        Records::factory()->count(100)->create();

        $expectedRecords = Records::paginate(10, ['*'], '', 1)->items();

        foreach ($expectedRecords as $dbRecord) {
            $response->assertJson(function(AssertableJson $json) use ($dbRecord) {
                $json->has('data', 10, function(AssertableJson $json) use ($dbRecord) {
                    $json->where('id', $dbRecord->id)
                        ->where('name', $dbRecord->name)
                        ->where('surname', $dbRecord->surname)
                        ->where('birth_date', $dbRecord->birth_date)
                        ->where('death_date', $dbRecord->death_date)
                        ->where('agency', $dbRecord->agency)
                        ->where('country_of_operation', $dbRecord->country_of_operation)
                        ->etc();
                });
            });
        }
    }
}

运行测试时出现如下错误:

1) Tests\Feature\Routes\SpyRoutesTest::testGetSpiesNoFilters
Unexpected properties were found on the root level.
Failed asserting that two arrays are identical.
--- Expected
+++ Actual
@@ @@
-Array &0 ()
+Array &0 (
+    0 => 'current_page'
+    2 => 'first_page_url'
+    3 => 'from'
+    4 => 'last_page'
+    5 => 'last_page_url'
+    6 => 'links'
+    7 => 'next_page_url'
+    8 => 'path'
+    9 => 'per_page'
+    10 => 'prev_page_url'
+    11 => 'to'
+    12 => 'total'
+)

/var/www/html/vendor/laravel/framework/src/Illuminate/Testing/Fluent/Concerns/Interaction.php:44
/var/www/html/vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:648
/var/www/html/tests/Feature/Routes/SpyRoutesTest.php:213
/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php:174

我想要验证响应JSON的data部分数据与分页查询返回的结果一致,请问该如何实现?


解决方案

1. 修正测试执行顺序

当前测试是先请求接口,再创建测试数据,这会导致接口返回空数据,完全不符合预期。必须先创建数据,再发起请求。

2. 正确处理分页字段的断言

错误提示的核心原因是:你在assertJson闭包里没有声明允许存在的顶层分页字段(比如current_page、total等),Laravel的断言会默认检查顶层字段是否完全匹配预期。另外,循环验证每条数据的写法效率低下,直接针对data数组做批量验证更合理。

修改后的测试代码如下:

use App\Model\Record;
use Tests\TestCase;
use Illuminate\Testing\Fluent\AssertableJson;

class RecordsEndpointTest extends TestCase
{
    public function testRecordsFetched()
    {
        // 1. 先创建测试数据
        Record::factory()->count(100)->create();

        // 2. 发起请求
        $response = $this->get('/records?page=1&limit=10');
        $response->assertStatus(200);

        // 3. 获取预期的分页数据集合
        $expectedRecords = Record::paginate(10, ['*'], '', 1)->items();

        // 4. 验证响应结构和数据
        $response->assertJson(function(AssertableJson $json) use ($expectedRecords) {
            // 声明允许存在的顶层分页字段
            $json->hasAll(['current_page', 'data', 'total', 'per_page'])
                 ->where('current_page', 1)
                 ->where('per_page', 10)
                 ->where('total', 100)
                 // 验证data数组的长度
                 ->has('data', 10)
                 // 遍历验证data数组中的每条数据
                 ->each('data', function(AssertableJson $item) use ($expectedRecords) {
                     $matchingRecord = collect($expectedRecords)->firstWhere('id', $item->id);
                     $item->where('id', $matchingRecord->id)
                          ->where('name', $matchingRecord->name)
                          ->where('surname', $matchingRecord->surname)
                          ->where('birth_date', $matchingRecord->birth_date)
                          ->where('death_date', $matchingRecord->death_date)
                          ->where('agency', $matchingRecord->agency)
                          ->where('country_of_operation', $matchingRecord->country_of_operation)
                          ->etc();
                 });
        });
    }
}

3. 简化验证的替代方案

如果只需要快速验证data部分和预期数据完全一致,可以直接将预期数据转成数组后断言:

// 前面步骤不变
$expectedData = Record::paginate(10, ['*'], '', 1)->toArray()['data'];

// 直接断言data部分匹配
$response->assertJsonFragment(['data' => $expectedData]);

或者更直接地验证完整结构:

$response->assertJson([
    'current_page' => 1,
    'per_page' => 10,
    'total' => 100,
    'data' => $expectedData,
]);

关键注意点

  • 测试数据必须在请求前创建,否则接口会返回空数据
  • 使用hasAll声明允许存在的顶层分页字段,避免“意外属性”错误
  • 用each方法遍历data数组验证每条数据,比循环调用assertJson更高效

内容的提问来源于stack exchange,提问作者Dimitrios Desyllas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 20:50:36