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

Laravel测试自定义异常时expectException()断言失败原因

问题背景

涉及的核心代码如下:

  1. 自定义异常类
namespace App\Exceptions;

use Exception;

class CustomException extends Exception
{
    public function render($request)
    {
        return response()->view('custom-exception');
    }
}
  1. 表单请求类中的异常抛出逻辑
class LoginRequest extends FormRequest
{
    public function authenticate()
    {
        if (! Auth::attempt($this->only('email', 'password'))) {
            throw new CustomException();
        }
    }
}
  1. 调用表单请求的控制器代码
class AuthenticatedSessionController extends Controller
{
    public function store(LoginRequest $request)
    {
        $request->authenticateMember();

        $request->session()->regenerate();

        return redirect()->intended(RouteServiceProvider::Home);
    }
}
  1. 测试代码
use Tests\TestCase;
use App\Models\User;
use App\Exceptions\CustomException;

class EmailVerificationTest extends TestCase
{
    public function test_email_verification_screen_can_be_rendered()
    {
        $user = User::factory()->create([
            'email_verified_at' => null,
        ]);

        // $this->expectException(CustomException::class); // 该断言无法通过

        $response = $this->post(
            '/login',
            [
                'email' => 'john@example.com',
                'password' => 'secret'
            ]
        );

        $response->assertViewIs('custom-exception');
        $this->assertInstanceOf(CustomException::class, $response->exception);
    }
}

测试现象:

  • 以下两个断言可以正常通过:
$response->assertViewIs('custom-exception');
$this->assertInstanceOf(CustomException::class, $response->exception);
  • $this->expectException(CustomException::class) 断言无法通过,报错信息:

Failed asserting that exception of type "App\Exceptions\CustomException" is thrown.


根本原因
  • PHPUnit自带的expectException断言,校验逻辑是检测测试执行流中是否有未被捕获的异常直接冒泡到PHPUnit运行层,只有异常没有被任何外层逻辑接住、直接抛到测试方法执行边界时,这个断言才会判定通过。
  • Laravel处理HTTP测试请求时,内置了全局异常拦截机制:请求执行全流程抛出的所有异常,都会先被框架核心的异常处理器(Illuminate\Foundation\Exceptions\Handler)捕获,不会直接向外冒泡到测试层。
    • 你为CustomException定义了render方法,框架捕获到异常后会直接调用该方法,生成返回custom-exception视图的响应,异常到这一步就已经被处理完成,不会继续向外抛出。
    • 之所以能通过$response->exception拿到异常实例、通过assertViewIs验证返回视图,是Laravel在处理异常生成响应时,特意把捕获到的异常实例挂载到了Response对象的exception属性上,方便测试做校验,不代表异常逃逸到了PHPUnit可以检测到的层级。
  • 顺带提两处代码笔误,虽然不影响当前断言失败的结果,但实际运行会报错:
    • 抛出异常时缺少new关键字,正确写法为throw new CustomException()
    • 控制器中调用的方法名是authenticateMember(),但表单请求内定义的验证方法名为authenticate(),方法名不匹配。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 23:00:17