如何让Laravel 9.x的单元测试与功能测试同时通过?
解决Laravel控制器分支逻辑导致单元/功能测试无法同时通过的问题
问题场景
我有两个测试类:Tests\Unit\BlogTest 和 Tests\Feature\BlogTest。在BlogController的Create方法中,当验证失败时,我通过app()->runningUnitTests()做分支判断:单元测试环境返回422响应,非单元环境则重定向到编辑路由并携带错误信息。但出现矛盾:
- 保留该条件时,单元测试通过,但功能测试失败
- 移除该条件时,功能测试通过,但单元测试失败
现有代码
BlogController的Create方法片段:
if ($validator->fails()) { if (app()->runningUnitTests()) { return response('Validation failed', 422); } return redirect()->route('blog_edit', ['id' => $id]) ->withErrors($validator) ->withInput(); }
Tests\Unit\BlogTest测试方法:
public function test_blog_create_validation() { $reqData = ['title' => null, 'author' => null, 'content' => null]; $response = $this->postJson( action([BlogController::class, 'create']), $reqData )->assertStatus(422); }
Tests\Feature\BlogTest测试方法:
public function test_blog_create_validation() { $response = $this->post('/blog', [ 'title' => '', 'author' => '', 'content' => '' ]); $response->assertSessionHasErrors([ 'title', 'author', 'content' ]); }
问题根源
用app()->runningUnitTests()区分响应格式的逻辑不合理:
- 单元测试中的
postJson本质是JSON请求,理应返回422格式的错误响应 - 功能测试中的普通
post是表单提交,理应返回重定向并携带会话错误 - 但
runningUnitTests()会强制所有单元测试环境下的请求返回422,不管请求类型;功能测试若在单元环境运行时,也会被错误触发422分支,导致断言失败
解决方案
把分支判断依据从「是否在单元测试环境」改成「请求是否需要JSON响应」,Laravel提供的$request->wantsJson()方法可以准确判断请求是否期望JSON格式响应(包括postJson发起的请求)。这样就能根据请求类型返回对应响应,与测试类型无关。
修改后的控制器代码
use Illuminate\Http\Request; // 在Create方法中注入Request实例 public function create(Request $request) { // ... 其他业务代码 if ($validator->fails()) { // 根据请求类型返回对应响应 if ($request->wantsJson()) { return response()->json(['errors' => $validator->errors()], 422); } return redirect()->route('blog_edit', ['id' => $id]) ->withErrors($validator) ->withInput(); } // ... 其他业务代码 }
测试验证
- 单元测试:
postJson请求触发wantsJson()为true,返回带错误信息的422 JSON响应,assertStatus(422)可正常通过 - 功能测试:普通
post请求触发重定向分支,错误信息存入会话,assertSessionHasErrors可正常断言
额外优化(可选)
如果需要兼容前端AJAX表单提交场景,可以把判断条件扩展为:
if ($request->wantsJson() || $request->ajax()) { // 返回JSON响应 }
内容的提问来源于stack exchange,提问作者Haroon
相关产品推荐
相关产品推荐

