如何断言异常类型和异常消息?
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。
- 虽然不是每个人都同意:https://softwareengineering.stackexchange.com/questions/184654/ive-been-told-that-exceptions-should-only-be-used-in-exceptional-cases-how-do
- 虽然 Java 有检查异常,但使用异常来表示所有类型的错误条件而不只是 exceptional 错误条件是有意义的 - 而C#/.NET 没有检查异常,因此需要人们阅读手写文档以查看声明的异常是什么(如果有)——这使得 .NET 中的错误处理 proper相当痛苦,除非你同意只在异常情况下抛出异常,而不是使用 return 类型来表示非异常错误情况。
- 即使你想抛出,你也不应该抛出
ArgumentNullException
来表示 HTTP 400 Bad Request 响应。
ArgumentException
class 及其子classes(ArgumentNullException
、ArgumentOutOfRangeException
等)只能用于指示失败的前提条件 -不是失败的后置条件也不是内部错误(为此使用 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);
这是我在 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。
- 虽然不是每个人都同意:https://softwareengineering.stackexchange.com/questions/184654/ive-been-told-that-exceptions-should-only-be-used-in-exceptional-cases-how-do
- 虽然 Java 有检查异常,但使用异常来表示所有类型的错误条件而不只是 exceptional 错误条件是有意义的 - 而C#/.NET 没有检查异常,因此需要人们阅读手写文档以查看声明的异常是什么(如果有)——这使得 .NET 中的错误处理 proper相当痛苦,除非你同意只在异常情况下抛出异常,而不是使用 return 类型来表示非异常错误情况。
- 即使你想抛出,你也不应该抛出
ArgumentNullException
来表示 HTTP 400 Bad Request 响应。ArgumentException
class 及其子classes(ArgumentNullException
、ArgumentOutOfRangeException
等)只能用于指示失败的前提条件 -不是失败的后置条件也不是内部错误(为此使用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);