Laravel单元测试怎么写 Laravel如何使用Pest进行测试 【质量】

5次阅读

laravel 中快速启用 pest 测试只需运行 php artisan pest:install,该命令自动创建配置、注册插件、生成示例测试并替换配置入口;需确保 laravel ≥9.27 且已安装 pestphp/pest 和 pestphp/pest-plugin-laravel。

Laravel单元测试怎么写 Laravel如何使用Pest进行测试 【质量】

怎么在 Laravel 里快速启用 Pest 测试

Pest 是 Laravel 官方推荐的测试框架,本质是 PHPUnit 的轻量封装,但语法更简洁。它不是独立安装的“新东西”,而是通过 pestphp/pest + pestphp/pest-plugin-laravel 插件与 Laravel 深度集成。直接用 php artisan pest:install 就能完成初始化,这命令会:自动创建 tests/Pest.php 入口、注册 Laravel 插件、生成示例测试文件、并把 phpunit.xml 替换为 pest.php 配置入口。

常见错误现象:php artisan pest:install 报错 “Command not found”——说明你没装 laravel/pint 或 Pest 包本身未被正确加载;或者 Laravel 版本低于 9.27(Pest 插件支持起点)。别手动改 composer.json 加包再 require-dev,先确认 Laravel 版本,再执行安装命令。

  • 确保已运行 composer require --dev pestphp/pest pestphp/pest-plugin-laravel
  • 运行 php artisan pest:install 后,tests/Feature/ExampleTest.php 会被自动转成 tests/Feature/ExampleTest.php → 实际是 tests/Feature/ExampleTest.php 被删,新建 tests/Feature/ExampleTest.php 并用 test() 函数重写
  • 首次运行 ./vendor/bin/pest 失败?检查 tests/Pest.php 是否存在,且是否包含 uses(TestsTestCase::class)->in('Feature', 'Unit');

test() 和 it() 怎么选,参数和作用域有啥区别

test()it() 都是 Pest 提供的顶层测试定义函数,语义上没强制区别,但社区习惯用 it() 写单个断言场景,test() 写多步骤逻辑。它们底层都调用 PHPUnit 的 TestCase::assertTrue() 等方法,但关键差异在闭包执行时的上下文:

  • test('creates user', function () { ... }):闭包内可直接访问 $this->get()$this->postJson() 等 Laravel 测试辅助方法,因为 Pest 自动绑定了 TestCase 实例
  • it('redirects when not authenticated', function () { ... }):行为完全一致,只是命名更聚焦“行为描述”
  • 如果闭包里用了 expect()(比如 expect($response)->status(302)),必须确保已启用 pest-plugin-assertions,否则报 Call to undefined function expect()
  • 别在 test() 闭包里 return 值——Pest 不捕获返回值,也不会自动断言;所有校验必须显式调用 expect()$this->assert...

数据库迁移和事务回滚为什么有时不生效

Pest 默认复用 Laravel 的 TestCase,所以 RefreshDatabase trait 依然有效,但容易被忽略的是:它只在继承 TestsTestCase 的测试类中起作用。而 Pest 的 test()it() 是函数式写法,不显式声明 class,所以依赖 trait 的自动注入机制——这个机制靠 uses() 函数配置。

常见错误现象:测试里插入数据,下个测试还能查到;或 artisan migrate:fresh 被反复执行拖慢速度。根本原因是你没在 tests/Pest.php 中正确配置 uses(),或在单个测试里误用了 DatabaseMigrations(它每次跑都重跑全部 migration,比 RefreshDatabase 慢得多)。

  • 确保 tests/Pest.php 包含 uses(TestsTestCase::class, RefreshDatabase::class)->in('Feature');
  • 不要在 it() 里手动调用 $this->artisan('migrate:fresh')——这会绕过事务控制,且破坏并发测试隔离性
  • 单元测试(Unit 目录)默认不启用数据库 trait,如需操作模型,得显式加 uses(RefreshDatabase::class) 到该测试文件顶部
  • sqlite 内存数据库(:memory:)在 Pest 中默认启用,但如果你改了 DB_CONNECTIONmysql,就得确保测试数据库允许无密码连接,否则 RefreshDatabase 会卡住

怎么测 API 返回结构、状态码和认证失败场景

Laravel 的 http 测试方法($this->get(), $this->postJson())在 Pest 中完全可用,但新手常卡在响应断言写法上:直接 dd($response) 看不到 JSON body,或用 assertJson() 时路径写错导致断言失败却没报具体哪条 key 缺失。

  • $response = $this->postJson('/api/users', [...]) 后,立刻接 $response->assertStatus(201) ——这是最稳的状态码断言方式,比 assertSuccessful() 更明确
  • 校验 JSON 结构优先用 $response->assertJson(['name' => 'John']),而不是 json_decode($response->getContent(), true) 手动取值——后者绕过了 Pest 的可读性增强(比如出错时会高亮显示 diff)
  • 测试未登录访问 API:先不调用 $this->actingAs(...),直接发请求,再用 $response->assertStatus(401)$response->assertJsonMissing(['user' => [...]])
  • 注意 assertJsonPath()assertJsonFragment() 的区别:前者要求完整匹配路径(如 'data.0.name'),后者只要 JSON body 中存在该键值对子集即可

真正难的不是写法,是测试边界——比如同时验证 Token 过期、IP 限流、字段长度超限这三个条件叠加时的响应,这时候单个 it() 很容易变成“大杂烩”。拆成三个独立测试,每个只动一个变量,不然失败时根本分不清是认证逻辑错了,还是验证规则错了,还是中间件顺序乱了。

text=ZqhQzanResources