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

.NET 6中Should.ThrowAsync无法捕获HttpClient.GetAsync请求异常

问题分析与解决方案

问题背景

我们有一个接口测试,目标是验证当接口抛出异常时返回500 InternalServerError。但从.NET Core 3.1升级到.NET 6后,Azure DevOps YAML部署管道运行测试时,会报告授权策略代码路径中的未处理异常,导致测试失败;而在Visual Studio 2022 Test Explorer本地运行测试时,虽然异常确实抛出,但测试仍能通过(因为返回了500状态码)。

相关代码如下:

接口端点

[HttpGet]
[Route("machines/{idMachine:guid}/admin")]
[Authorize(Policy = Policies.RequireMachineCustomerCheck)]
public IActionResult MachineEditor(Guid idMachine)
{
    // 机器查询逻辑...

    if (machine == null)
    {
        throw new Exception("Exception");
    }
}

测试代码

[Fact]
public async Task MachineNotFoundShouldThrowException()
{
    var idMachine = Guid.NewGuid();
    var uri = new Uri($"/machines/{idMachine}/admin", UriKind.Relative);
    var httpClient = new HttpClient();

    var response = await httpClient.GetAsync(uri);
    response.StatusCode.ShouldBe(HttpStatusCode.InternalServerError);
}

授权Handler代码

public class MachineCustomerCheckHandler : AuthorizationHandler<MachineCustomerCheck>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MachineCustomerCheck requirement)
    {
        // 机器查询逻辑...

        if (machine == null)
        {
            throw new Exception("Exception");
        }
    }
}

核心原因

  1. .NET 6中间件异常处理机制变化:.NET 6对未处理异常的捕获逻辑做了调整,授权中间件中抛出的异常在Azure DevOps的测试运行上下文里,会被测试运行器识别为未处理异常上报,而不是被框架自动转换为500响应。
  2. 授权Handler的错误实现:ASP.NET Core授权Handler的设计初衷是通过context.Succeed()/context.Fail()标记授权结果,而非直接抛出异常。直接抛出异常会打破中间件管道的正常流程。
  3. 测试方式不规范:直接使用HttpClient调用接口,没有模拟完整的ASP.NET Core管道,导致异常处理中间件无法正常捕获并转换异常。

解决方案

1. 修复授权Handler的逻辑

将授权Handler中的抛出异常改为标记授权失败,或把机器不存在的检查逻辑移到接口方法内:

public class MachineCustomerCheckHandler : AuthorizationHandler<MachineCustomerCheck>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MachineCustomerCheck requirement)
    {
        // 从路由获取机器ID
        if (!context.Resource is HttpContext httpContext || 
            !httpContext.Request.RouteValues.TryGetValue("idMachine", out var idValue) ||
            !Guid.TryParse(idValue.ToString(), out Guid idMachine))
        {
            context.Fail();
            return Task.CompletedTask;
        }

        // 机器查询逻辑
        var machine = // 你的查询代码

        if (machine == null)
        {
            // 标记授权失败,框架会返回403 Forbidden
            // 如果需要返回500,可将机器检查逻辑移到接口方法中
            context.Fail();
            return Task.CompletedTask;
        }

        context.Succeed(requirement);
        return Task.CompletedTask;
    }
}

2. 使用WebApplicationFactory搭建测试服务器

通过WebApplicationFactory模拟完整的ASP.NET Core管道,确保异常被异常处理中间件正确捕获:

public class MachineTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;

    public MachineTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory;
    }

    [Fact]
    public async Task MachineNotFoundShouldReturn500()
    {
        var idMachine = Guid.NewGuid();
        var client = _factory.CreateClient();

        var response = await client.GetAsync($"/machines/{idMachine}/admin");
        response.StatusCode.ShouldBe(HttpStatusCode.InternalServerError);
    }
}

3. 确保异常处理中间件顺序正确

在Program.cs中,将异常处理中间件放在管道最前端,确保能捕获所有后续中间件的异常:

var builder = WebApplication.CreateBuilder(args);

// 注册服务...
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy(Policies.RequireMachineCustomerCheck, policy => 
        policy.Requirements.Add(new MachineCustomerCheck()));
});

var app = builder.Build();

// 异常处理中间件必须放在最前面
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// 其他中间件...
app.UseAuthorization();

app.MapControllers();

app.Run();

本地与Azure DevOps表现差异说明

本地VS测试环境中,开发模式的异常处理中间件会自动捕获管道内的异常并转换为响应,且测试运行器不会将此类异常标记为未处理;而Azure DevOps的测试运行环境通常使用生产模式配置,对未处理异常的检测更严格,导致异常被上报为测试失败。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 14:03:21