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

ASP.NET Core 8 Web API:编写XUnit测试验证BadRequest响应

解决ASP.NET Core Web API中BadRequest场景的XUnit测试问题

问题核心在于你的ApiClient.SendAsync方法大概率依赖了HttpClient的默认行为——当收到4xx/5xx状态码时,会自动抛出HttpRequestException(通常是调用了EnsureSuccessStatusCode()导致)。要优雅测试BadRequest场景,需要先改造客户端请求逻辑,让它不自动抛出异常,而是完整返回响应状态和内容。

步骤1:改造ApiClient.SendAsync方法

修改该方法,取消自动抛出异常的逻辑,同时支持返回成功响应或错误响应:

public async Task<(TResponse? SuccessResponse, TError? ErrorResponse, HttpStatusCode StatusCode)> SendAsync<TResponse, TError>(
    HttpMethod method,
    string url,
    Action<HttpRequestMessage>? configureMessage = null,
    object? model = null,
    CancellationToken ct = default)
    where TError : class
{
    using var request = new HttpRequestMessage(method, url);

    configureMessage?.Invoke(request);

    if (model != null)
    {
        request.Content = new JsonContent(model, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
    }

    // 发送请求但不自动抛出异常
    using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, ct);
    var statusCode = response.StatusCode;

    if (response.IsSuccessStatusCode)
    {
        var successContent = await response.Content.ReadFromJsonAsync<TResponse>(ct: ct);
        return (successContent, null, statusCode);
    }
    else
    {
        var errorContent = await response.Content.ReadFromJsonAsync<TError>(ct: ct);
        return (null, errorContent, statusCode);
    }
}

步骤2:调整测试用例

直接调用改造后的SendAsync方法,获取状态码和错误信息,无需try/catch:

public async Task Create_WhenDuplicateName_ReturnsBadRequest()
{
    using var scope = _lab.AppFactory.Services.CreateScope();
    var provider = scope.ServiceProvider;

    // Arrange:先创建同名许可证,模拟重复场景
    await _lab.InitAsync(provider, true);
    var licensesRepository = provider.GetRequiredService<IVdcQueryRepository<Licenses>>();
    
    // 提前插入同名数据
    await licensesRepository.AddAsync(new Licenses { Name = "New license", MaxCpu = 8 }, CancellationToken.None);
    await licensesRepository.SaveChangesAsync(CancellationToken.None);

    var request = new CreateUpdateRequest
    {
        Name = "New license", // 重复名称触发验证
        MaxCpu = 8,
        ParentId = null
    };

    // Act
    var (_, errorResponse, statusCode) = await _client.ApiClient.SendAsync<LicenseResponse, ValidationProblemDetails>(
        HttpMethod.Post,
        "/api/license",
        message => message.AddAuthorization(_lab.Identity, _options.ApiKey),
        model: request);

    // Assert
    Assert.Equal(HttpStatusCode.BadRequest, statusCode);
    Assert.NotNull(errorResponse);
    Assert.Contains(errorResponse.Errors.Values, errors => 
        errors.Contains("There is another license with the same name. Please, choose a different one."));
}

额外优化:验证器性能提升

你的验证器查询逻辑可以简化为更高效的异步判断,避免不必要的内存占用:

public async ValueTask<IResponse> ValidatorAsync(CreateCommand request, RequestValidator<CreateCommand> validator, CancellationToken ct = default)
{
    var errorMsg = "There is another license with the same name. Please, choose a different one.";
    validator.RuleFor(r => r.CreateLicense.Name)
        .MustAsync(async (name, ct) => 
            !await _repository.FindAll().AnyAsync(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase), ct))
        .WithMessage(errorMsg);

    return request.OkResponse();
}

用AnyAsync直接判断存在性,比ToList+ToArray更高效,同时使用StringComparison.OrdinalIgnoreCase避免文化差异问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 14:23:19