67465364_491902344708962_1591338503323516928_n.jpg

Hi.

Welcome to my blog. I document my ideas and productivity hacks.

Unboxing .NET Core - Integration Tests for Request/Response Pipeline

I have been working with .NET Core for the past couple of weeks and every time I want to do something that I used to do with a 3rd party library .NET Core already has an option out of the box.

Today I'm going to talk about Integration Testing using .NET Core.

Let's say that we have created a REST API for EBasket (An E-commerce Web App that I'm building as I post entries in this blog), and we have an endpoint to get the last orders since a specific date.

[GET] /api/orders?since=2019-01-01

Let's say that we want to guarantee that the parameter since is always provided when calling the orders endpoint.

Adding this validation is something pretty simple to do using .NET Core MVC, only things needed are

  • An ApiController
  • Attribute [Required] decorating the query parameter "since"

However, how do we guarantee that if that is modified the behavior of the validation does not break?

Having something like this

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;

namespace EBasket.WebApi.Controllers
{
    [Route("/api/[controller]")]
    [ApiController]
    public class OrdersController : ControllerBase
    {
        [HttpGet]
        public IEnumerable<Order> GetOrders([FromQuery][Required]DateTime since)
        {
            return new List<Order>();
        }
    }

    public class Order
    {
    }
}

When calling [GET] /api/orders without providing the query parameter since will result in something like this:

{"since":["The since field is required."]}

I mean, what could go wrong, why bother adding a test for this?

Let's say that we also want to filter by status so we decide to modify our GetOrders method and by coincidence, we removed the [Required] attribute from the since query parameter.

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;

namespace EBasket.WebApi.Controllers
{
    [Route("/api/[controller]")]
    [ApiController]
    public class OrdersController : ControllerBase
    {
        public class QueryParameters
        {
            public DateTime Since { get; set; }
            public string Status { get; set; }
        }

        [HttpGet]
        public IEnumerable<Order> GetOrders([FromQuery]DateTime since, string status)
        {
            return new List<Order>();
        }
    }

    public class Order
    {
    }
}

Now when calling [GET] /api/orders without providing the query parameter since won't validate. Oops, we removed the [Required] attribute by mistake.

Please forgive my extensive and dramatic example but I just wanted to show my point, we could have avoided breaking the behavior of this if we have had an Integration Test in place.

So, let's get to it!

We would need a Tests project, in this case, I will use a xUnit project, and I will add this 2 NuGet packages

Microsoft.AspNetCore.App
Microsoft.AspNetCore.Mvc.Testing

Make sure that your test project is using the Web SDK in the project file.

Microsoft.NET.Sdk.Web

In your project (csproj) the first line should be

<Project Sdk="Microsoft.NET.Sdk.Web">

Now let's test this!, so having a test along these lines will guarantee that when calling the [GET] /api/orders/ without the required parameters will result in a bad request.

using System.Net;
using System.Threading.Tasks;
using EBasket.WebApi;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;

namespace EBasket.WebApiTests
{
    public class OrdersControllerTests  : IClassFixture<WebApplicationFactory<Startup>>
    {
        private readonly WebApplicationFactory<Startup> _factory;

        public OrdersControllerTests(WebApplicationFactory<Startup> factory)
        {
            _factory = factory;
        }

        [Fact]
        public async Task Given_Query_Parameter_Since_Is_Not_Provided_When_Calling_Get_Orders_It_Should_Throw_Bad_Request()
        {
            var client = _factory.CreateClient();

            var response = await client.GetAsync("/api/orders");

            Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
        }
    }
}

Now when you execute your test.

dotnet test

You should see an error because the /api/orders endpoint must throw an error when the query parameter since is not provided.

[xUnit.net 00:00:02.58]     EBasket.WebApiTests.OrdersControllerTests.Given_Query_Parameter_Since_Is_Not_Provided_When_Calling_Get_Orders_It_Should_Throw_Bad_Request [FAIL]
Failed   EBasket.WebApiTests.OrdersControllerTests.Given_Query_Parameter_Since_Is_Not_Provided_When_Calling_Get_Orders_It_Should_Throw_Bad_Request
Error Message:
 Assert.Equal() Failure
Expected: BadRequest
Actual:   OK

Fixing this will require to add back the [Required] attribute to the query parameter since in the controller.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;

namespace EBasket.WebApi.Controllers
{
    [Route("/api/[controller]")]
    [ApiController]
    public class OrdersController : ControllerBase
    {

        [HttpGet]
        public IEnumerable<Order> GetOrders([FromQuery][Required]DateTime since, string status)
        {
            return new List<Order>();
        }
    }

    public class Order
    {
    }
}

Now your test should pass!

And that is how you can write integration tests for your request/response pipelines using .NET Core.

If you want to learn more, you can go here

You can view the source code here

Short Book Review - Clean Coder by Robert C. Martin

.NET Core On Windows - How to execute unit test before I commit?