如何断言异常类型和异常消息?

How to assert exception type and exception message?

这是我在 XUnit 中的测试方法。

    [Fact]
    public async Task AddCampaign_ReturnBadRequestWhenDateIsInvalid()
    {
        var client = _factory.CreateClient();
        string title = string.Format("Test Add Campaign {0}", Guid.NewGuid());
        var campaignAddDto = new CampaignDTORequest
        {
            Title = title
        };
        var encodedContent = new StringContent(JsonConvert.SerializeObject(campaignAddDto), Encoding.UTF8, "application/json");

        var response = await client.PostAsync("/api/Campaign/add", encodedContent);
        var responseString = await response.Content.ReadAsStringAsync();
        var result = JsonConvert.DeserializeObject<CampaignDTOResponse>(responseString);

        Assert.False(response.IsSuccessStatusCode);
        Assert.ThrowsAsync<ArgumentNullException>(()=> client.PostAsync("/api/Campaign/add", encodedContent));
    }

第一个断言有效。我坚持第二个断言。如何断言异常类型 (ArgumentNullException) 及其异常消息?

这是服务方式

    public async Task<Campaign> AddCampaignAsync(Campaign campaign)
    {            
        if (campaign.StartDate.Equals(DateTime.MinValue)) {
            throw new ArgumentNullException("Start Date cannot be null or empty.");
        }
        
        await _context.Campaigns.AddAsync(campaign);
        await _context.SaveChangesAsync();
        return campaign;
    }

根据雷洋的线索更新

var exceptionDetails = Assert.ThrowsAsync<ArgumentNullException>(() => client.PostAsync("/api/Campaign/add", encodedContent));
Assert.Equal("Start Date cannot be null or empty.", exceptionDetails.Result.Message);

但是还是不行。

System.AggregateException : One or more errors occurred. (Assert.Throws() Failure Expected: typeof(System.ArgumentNullException) Actual: (No exception was thrown))

尝试了 Dai 的解决方案,但仍然出错。

Assert.Throws() Failure
Expected: typeof(System.ArgumentNullException)
Actual:   (No exception was thrown)

这是我的API方法。

 public async Task<ActionResult<CampaignDTOResponse>> AddCampaign([FromBody] CampaignDTORequest newCampaign)
    {
        try
        {
            var campaign = _mapper.Map<Campaign>(newCampaign);
            campaign = await _campaignService.AddCampaignAsync(campaign);
            var campaignDtoResponse = _mapper.Map<CampaignDTOResponse>(campaign);
            return CreatedAtAction(nameof(GetCampaignById), new { id = campaignDtoResponse.Id }, campaignDtoResponse);
        }
        catch (Exception ex)
        {
            _logger.LogError(0, ex, ex.Message);
            return Problem(ex.Message);
        }
    }

更新:我将检查从服务移至 api。

if (newCampaign.StartDate.Equals(DateTime.MinValue))
{
    return BadRequest("Start Date cannot be null or empty.");
}

我断言如下。

Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.Equal("Start Date cannot be null or empty.", responseString);

Assert.ThrowsAsync is still an async Task method,因此您需要 await 确保任务继续(执行实际断言)可以 运行 正确:

[Fact]
public async Task AddCampaign_Return_bad_request_when_date_is_invalid()
{
    [...]

    Assert.False(response.IsSuccessStatusCode);
    await Assert.ThrowsAsync<ArgumentNullException>(()=> client.PostAsync("/api/Campaign/add", encodedContent));
}

然而...

  • ...请重新考虑您的设计:Exceptions should be exceptional
  • 即使你想抛出,你也不应该抛出 ArgumentNullException 来表示 HTTP 400 Bad Request 响应。
    • ArgumentException class 及其子classes(ArgumentNullExceptionArgumentOutOfRangeException 等)只能用于指示失败的前提条件 -不是失败的后置条件也不是内部错误(为此使用 InvalidOperationException)。
    • 我个人认为网络服务客户端不应该曾经对任何响应抛出异常,除非它实际上一个“异常”响应或情况。
    • 如果您使用 NSwag 生成 Web 服务客户端,那么它会为您生成 ApiException<TResponse>,这会更有用。
    • 虽然我更喜欢 return 所有 合理的 可能的反应(即 [ProducesResponseType] 声明的任何反应)的可区分联合。

您可以使用 ThrowsAsync 捕获异常并断言它:

// Act
var exception = await Assert.ThrowsAsync<ArgumentNullException>(()=> client.PostAsync("/api/Campaign/add", encodedContent));

// Assert
Assert.Equal(exception.Message, "message to compare");

// Act
Action action = async () => await client.PostAsync("/api/Campaign/add", encodedContent);
var ex = Record.Exception(action);

// Assert
Assert.NotNull(ex);
Assert.IsType<ArgumentNullException>(ex);