第一个it测试用pest编写:在tests/feature/userstest.php中写it('returns a list of users', function() { $user = user::factory()->create(); $this->get('/api/users')->assertstatus(200)->assertjsonstructure(['data' => [['id', 'name', 'email']]]); }),需先运行php artisan pest:install并确保路由、控制器未实现以触发预期失败。

it 测试写不通过,不是因为语法错,而是你还没实现路由、控制器或数据库逻辑——TDD 的第一步就是让测试明确失败,再补全对应代码。
怎么写第一个 it 测试(Pest)
用 Pest 写测试,核心是用 it() 函数描述行为,而不是类方法名。它默认绑定 Laravel 的 TestCase,所以能直接调用 $this->get()、$this->postJson() 等。
常见错误现象:运行 ./vendor/bin/pest 报错 Target class [UserController] does not exist 或 Class 'User' not found —— 这说明测试已正确执行到“访问路由”这步,但后端还没搭。
- 测试文件必须放在
tests/Feature/下,命名如UsersTest.php - 记得加
uses(RefreshDatabase::class),否则数据库状态会污染后续测试 - 不要在测试里手动
new UserController(),Laravel 的 HTTP 测试机制依赖容器自动解析 - 如果用模型工厂,确认
User::factory()->create()调用前已定义好工厂(database/factories/UserFactory.php)
assertJsonStructure 和 assertJson 选哪个
assertJsonStructure 只校验字段是否存在和嵌套层级,不校验值;assertJson 则严格比对整个 JSON 片段的键+值(含类型)。
容易踩的坑:用 assertJson 断言时间字段时,$user->created_at 是 Carbon 实例,JSON 序列化后带毫秒和时区,而硬编码字符串很难完全匹配。
- 校验结构用
assertJsonStructure(['data' => ['id', 'name', 'email']]) - 校验具体值且确定可控时,用
assertJson(['data' => ['name' => $user->name]]) - 涉及时间、ID 自增、token 等动态值,优先用
assertJsonFragment或assertJsonMissing - 返回分页数据时,
assertJsonStructure对data.*支持有限,建议写成'data' => [['id', 'name']]
认证测试里 Passport::actingAs 不生效
现象是请求返回 401,但测试里明明调了 Passport::actingAs($user)。根本原因通常是:没发 token 到请求头,或用户没绑 client。
- 确保已运行
php artisan passport:install,且config/auth.php中apiguard 使用passport -
Passport::actingAs($user)只设置当前测试上下文的认证状态,不自动生成 Authorization header;HTTP 测试需显式传 token:$this->withToken($user->createToken('test')->accessToken) - 如果用
Sanctum,换用$user->withPersonalAccessToken()->createToken('test')+withToken() - 别在
beforeEach里全局actingAs,它对 HTTP 测试无效,只影响 Eloquent 模型操作上下文
为什么测试通过了,但生产环境还是出错
最常被忽略的是中间件和环境差异。Laravel 测试默认禁用中间件(WithoutMiddleware Trait),但有些逻辑藏在中间件里,比如权限检查、请求日志、CORS 头。
另一个隐形陷阱是数据库驱动:测试用 SQLite 内存库,生产用 MySQL。某些 SQL 行为不一致,比如 GROUP BY 严格模式、空字符串与 NULL 的比较。
- 需要测中间件时,去掉
WithoutMiddleware,改用RefreshDatabase+ 显式启用:$this->withMiddleware() - 复杂查询逻辑,用
DB::listen()在测试中打印实际 SQL,对比 SQLite 和 MySQL 执行结果 - 时间相关断言避免用
now(),改用Carbon::setTestNow()锁定时间点 - 配置项如
APP_URL、BROADCAST_DRIVER在测试中默认是testing,若业务代码读取这些值,得在测试里putenv('APP_URL=http://localhost')











