Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to assert exception type and exception message?

Tags:

c#

xunit

This is my test method in 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));
    }

The first assert is works. I am stuck with second assert. How do I assert both exception type (ArgumentNullException) and its exception message?

This is the service method

    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;
    }

Updated after the clue from Lei Yang.

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

But still doesn't work.

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

Tried the solution from Dai but still got error.

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

This is my API method.

 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);
        }
    }

Updated: I move the checking from service to api.

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

and I assert them like below.

Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.Equal("Start Date cannot be null or empty.", responseString);
like image 674
Steve Avatar asked Apr 24 '26 04:04

Steve


1 Answers

Assert.ThrowsAsync is still an async Task method, so you need to await that to ensure the task continuation (that does the actual assert) can run correctly:

[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));
}

However...

  • ....please reconsider your design: Exceptions should be exceptional.
    • Though not everyone agrees: https://softwareengineering.stackexchange.com/questions/184654/ive-been-told-that-exceptions-should-only-be-used-in-exceptional-cases-how-do
    • Though as Java has checked-exceptions, it can make sense to use exceptions to represent all kinds of error-conditions instead of just exceptional error conditions - whereas C#/.NET does not have checked-exceptions and so requires people to read hand-written documentation to see what the declared exceptions are, if any - which makes proper error-handling in .NET rather painful unless you agree to throw only in exceptional conditions and instead use return-types to represent non-exceptional error conditions.
  • Even if you want to throw, you should not be throwing ArgumentNullException to represent HTTP 400 Bad Request responses.
    • The ArgumentException class and its subclasses (ArgumentNullException, ArgumentOutOfRangeException, etc) should only be used to indicate a failed precondition - not a failed postcondition nor internal error (use InvalidOperationException for that).
    • Personally I don't think that web-service clients should ever throw exceptions for any response unless it's actually an "exceptional" response or situation.
    • If you use NSwag to generate web-service clients then it will generate ApiException<TResponse> for you, which is far more useful.
    • Though my preference is to return a discriminated-union of all reasonable possible responses (namely, whatever is declared by [ProducesResponseType]).
like image 189
Dai Avatar answered Apr 26 '26 18:04

Dai