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
相关产品推荐
相关产品推荐

