Unit testing ASP.NET Core Web API

August 5th 2022 ASP.NET Core Unit Testing

In a previous blog post, I looked at integration testing of ASP.NET Core Web APIs. But often unit tests make more sense. So instead of checking the already serialized responses from the REST service, you would check the value returned by the controller action method before it is serialized into an HTTP response by the ASP.NET Core runtime.

Suppose we want to unit test the following action method:

public ActionResult<Data> Get(int id)
{
    if (id <= 0)
    {
        return this.BadRequest(
            new ErrorDetails("invalid.id", "Id must be non-negative."));
    }

    if (id == 1)
    {
        return this.Ok(new Data(1, "one"));
    }
    else
    {
        return this.NotFound(
            new ErrorDetails("not.found", $"No data found for id {id}."));
    }
}

Setup is much simpler than for integration testing. You only need to instantiate the controller class. An in-process web server is not required:

var controller = new DataController();
var result = controller.Get(1);

The surprising thing for me was that the Value property of the returned ActionResult<Data> instance did not contain the value returned by the action method, and the following assertion failed because the actual property value was null:

var expected = new Data(1, "one");
result.Value.Should().BeEquivalentTo(expected);

To access the returned value, I had to cast the Result property of the returned ActionResult<Data> to OkObjectResult and check its Value property instead:

var okResult = result.Result as OkObjectResult;

okResult.Should().NotBeNull();
okResult?.StatusCode.Should().Be(200);
okResult?.Value.Should().BeEquivalentTo(expected);

Of course, the cast to OkObjectResult should only be used for 200 OK responses. For other responses, the Result must be cast to their respective classes returned by the action method. For our example action method, these would be:

  • BadRequestObjectResult for the 400 Bad Request response:

    [Test]
    public void GetShouldReturn400ForInvalidIds()
    {
        var controller = new DataController();
    
        var result = controller.Get(0);
        var badRequestResult = result.Result as BadRequestObjectResult;
    
        var expected = new ErrorDetails("invalid.id", "Id must be non-negative.");
    
        badRequestResult.Should().NotBeNull();
        badRequestResult?.StatusCode.Should().Be(400);
        badRequestResult?.Value.Should().BeEquivalentTo(expected);
    }
    
  • NotFoundObjectResult for the 404 Not Found response:

    [Test]
    public void GetShouldReturn404ForNotFoundIds()
    {
        var controller = new DataController();
    
        var result = controller.Get(2);
        var notFoundResult = result.Result as NotFoundObjectResult;
    
        var expected = new ErrorDetails("not.found", "No data found for id 2.");
    
        notFoundResult.Should().NotBeNull();
        notFoundResult?.StatusCode.Should().Be(404);
        notFoundResult?.Value.Should().BeEquivalentTo(expected);
    }
    

For other responses, you can always check the return type of the controller method you call to generate the response, e.g. for the 404 Not Found response above:

public virtual NotFoundObjectResult NotFound([ActionResultObjectValue] object? value);

You can find the full source code for the sample project in my GitHub repository.

You can unit test ASP.NET Core Web API controller action methods like any other class methods if you do not need to cover deserialization and serialization in your tests. However, how you can check the ActionResult<T> values returned by the action method is not quite as intuitive. In this post, I give you some guidance on how to do that.

Get notified when a new blog post is published (usually every Friday):

If you're looking for online one-on-one mentorship on a related topic, you can find me on Codementor.
If you need a team of experienced software engineers to help you with a project, contact us at Razum.
Copyright
Creative Commons License