How to Unit Test Middleware in ASP.NET Core

In this article, let's discuss about how we can unit test a middleware component in ASP.NET Core using xUnit with an example.

Introduction

Unit Testing refers to testing the functionality of a component in isolation. A Middleware is an individual component that is responsible for a single functionality in a request pipeline. In this article, let’s talk about how we can “Unit Test” a “Middleware” component in ASP.NET Core.

Middlewares are one of the most powerful components in the ASP.NET Core framework, using which we can perform a particular functionality over an incoming request or an outgoing response.

We can use them for practical purposes such as Logging, Authorization, Routing and for more complex scenarios that suit our requirements. Why are we talking about all this now? Just to understand that a custom Middleware class can hold some business logic which does require testing for any errors.

And how can we do this better? by writing Unit Tests for the logic that happens in a Middleware just to ensure that everything happens as expected when an actual request passes by.

How to Unit Test Middleware in .NET Core

To illustrate this, let’s take the example of a simple Middleware component inside our ReadersAPI which looks into the incoming Request Headers for an “X-Request-ID” header that uniquely identifies a request, and places it inside HttpContext.Items dictionary.

If the incoming Request Headers don’t contain this header, the Middleware generates a Guid to indicate a RequestId and places the same inside the Items dictionary.

For starters, HttpContext.Items is a dictionary which we can use to temporarily store data that persists across a single Request. It is a convenient data structure we can use to pass along persistent data across components within a single Request scope.

We set the “X-Request-ID” header value inside our HttpContext.Items and use it to aggregate all the Logs recorded for this particular Request in our Logging framework, which is something out of the scope of our goal.

Since this exercise occurs at a Request level, we can use a Middleware which gets this job done for every incoming request, before the request reaches its intended handler.

The component looks like below.

namespace ReadersApi.WebApi.Middlewares
{
    public class SetRequestContextItems
    {
        private readonly RequestDelegate _next;
        public const string XRequestIdKey = "X-Request-ID";


        public SetRequestContextItems(RequestDelegate next)
        {
            _next = next;
        }


        public async Task InvokeAsync(HttpContext context)
        {
            // Read the Incoming Request Headers for a xRequestId Header
            // If present in the Header pull it from the Headers
            // Else Create a new Guid that represents the xRequestId


            //Set the xRequestId value thus obtained into HttpContext.Items Dictionary


            var headers = context.Request.Headers
                    .ToDictionary(x => x.Key, x => x.Value.ToString());


            string xRequestIdValue = string.Empty;


            if (headers.ContainsKey(XRequestIdKey))
            {
                xRequestIdValue = headers[XRequestIdKey];
            }
            else
            {
                xRequestIdValue = Guid.NewGuid().ToString();
            }


            context.Items.Add(XRequestIdKey, xRequestIdValue);


            await _next.Invoke(context);
        }
    }
}

We can then register this component into our Request pipeline inside the Startup class as below.

namespace ReadersApi.WebApi
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }


        public IConfiguration Configuration { get; }


        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
        }


        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            // other middleware registrations
            app.UseMiddleware<SetRequestContextItems>();
        }
    }
}

To test this, let’s just add a Test Endpoint that returns all the contents of HttpContext.Items dictionary for every request.

namespace ReadersApi.WebApi.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class ContextItemsController : ControllerBase
    {
        [HttpGet]
        public Dictionary<string, string> Get()
        {
            return this.HttpContext
                .Items
                .ToDictionary(x => x.Key.ToString(), x => x.Value.ToString());
        }
    }
}

Which returns this when invoked.

{
    "X-Request-ID": "44368617-07de-4819-ac8d-e07161b7a394"
}

Using xUnit to write Unit Tests

Everything looks great till this point. Now let’s add a Test class that simulates this functionality and checks if the Middleware works fine for the two possible scenarios of this functionality:

  • How it behaves when we pass a xRequestId header in the Request
  • How it behaves when we don’t pass a xRequestId header

We will use xUnit to write our Unit Test scripts on the SetRequestContextItems Middleware class for varying input cases.

We write these scripts inside the ReadersApi.Tests project which takes a project reference of the ReadersApi.WebApi project and is an xUnit Test project.

namespace ReadersApi.Tests
{
    public class SetRequestContextItemsTests
    {
        [Fact]
        public async Task
          CheckIfRequestIdContextItemIsSet_WhenRequestHeadersContainXRequestId()
        {
            // Test Logic for the scenario happens here
        }


        [Fact]
        public async Task
          CheckIfRequestIdContextItemIsSet_WhenRequestHeadersDontContainXRequestId()
        {
            // Test Logic for the scenario happens here
        }
    }
}

How to Mock Middleware dependencies for Unit Testing

Writing Unit Tests is simple: we follow the 3A philosophy – Arrange, Act, Assert where we create an instance of the unit we’re supposed to test with unreal dependencies if it needs any; and test how its own logic works in ideal conditions irrespective of its dependencies.

For any Middleware, we have two dependencies which we need to take care of:

  • HttpContext – represents the incoming Request Context
  • RequestDelegate – represents the next Action in the pipeline which needs to be invoked once the current Middleware completes execution

We need to pass the RequestDelegate instance to our Middleware constructor, while the HttpContext needs to be supplied as a parameter for the InvokeAsync() method.

Since we’re doing a UnitTest on the Middleware class as any other concrete class and hence a real HttpContext can’t be brought in, same with the RequestDelegate. Hence we shall supply Mock dependencies to our Middleware class to get things done.

We shall use Moq framework to create Mock dependencies for us to use in our Unit Tests.

Mocking the HttpContext

Since HttpContext is an abstract class, we can’t directly create an instance of it. But to supply an instance of HttpContext for our purpose, we can create a Mock HttpContext class and pass it off.

Mock<HttpContext> httpContextMoq = new Mock<HttpContext>();


// some setup
HttpContext context = httpContextMoq.Object;

But the real question here is: do we need to mock everything inside a HttpContext for our purpose?

The answer is: no. It’s not really required to sit and mock each and every attribute of HttpContext object in order to create a real Mock HttpContext for our purpose.

Let’s just look at what parts of HttpContext are we using in our business logic in the Middleware class. We use only for two reasons:

  • To read data from HttpContext.Request.Headers
  • To insert data into HttpContext.Items

So we can just create a Mock HttpContext and set up only the Request.Headers and Items dictionary so that the Test won’t throw us an Object Reference when it tries to access these properties from the mock HttpContext object.

Let’s set up the HttpContext object for our first Test case – when XRequestIdKey is present in the Request Headers.

// Arrange

// Mocking the HttpContext.Request.Headers
// when xRequestIdKey Header is present
var headers = new Dictionary<string, StringValues>() {
    { SetRequestContextItems.XRequestIdKey, "123456" }
};

var httpContextMoq = new Mock<HttpContext>();
httpContextMoq.Setup(x => x.Request.Headers)
    .Returns(new HeaderDictionary(headers));
httpContextMoq.Setup(x => x.Items)
    .Returns(new Dictionary<object, object>());

var httpContext = httpContextMoq.Object;

Observe that we’re also setting up the Items dictionary so that when the Middleware tries to push some value into the dictionary it shouldn’t be acting on a null property, which is the default for any property in the Mocked object that is not setup.

So we pass an empty dictionary so that later on we can check whether this dictionary has any values added in it.

How to Fake RequestDelegate of Middleware

As mentioned before, RequestDelegate represents the next Middleware which needs to be invoked in the Request pipeline. Since we don’t need to invoke any other Task later on in our Test, we can just Fake it with an empty Action that just does nothing.

We just create a new RequestDelegate object and pass in an action that simply returns a completed task with a non-error code.

// Arrange
var requestDelegate = new RequestDelegate((innerContext) => Task.FromResult(0));

Finally, we can just create an instance of our Middleware class and pass these mock/fake dependencies we just created and run to see how this works.

The complete Test method looks like below:

namespace ReadersApi.Tests
{
    public class SetRequestContextItemsTests
    {
        [Fact]
        public async Task
        CheckIfRequestIdContextItemIsSet_WhenRequestHeadersContainXRequestId()
        {
            // Arrange


            // Create an instance of SetRequestContextItems() class
            // Call the InvokeAsync() method
            // Check if the resultant HttpContext.Items Dictionary
            // contains xRequestIdKey with some Value


            // SetRequestContextItems() class requires two dependencies
            // HttpContext and RequestDelegate
            // We need to mock HttpContext.Request and RequestDelegate for our case
            // The Business Logic runs on HttpContext.Request.Headers Dictionary and
            // not any other HttpContext attribute,
            // so we can safely mock only the Request.Headers part
            // RequestDelegate is an ActionDelegate that invokes the next Task
            // Hence we can just pass a dummy Action as the next Task to perform


            // Mocking the HttpContext.Request.Headers when xRequestIdKey Header is present
            var headers = new Dictionary<string, StringValues>() {
            { SetRequestContextItems.XRequestIdKey, "123456" }
        };


            var httpContextMoq = new Mock<HttpContext>();
            httpContextMoq.Setup(x => x.Request.Headers)
                .Returns(new HeaderDictionary(headers));
            httpContextMoq.Setup(x => x.Items)
                .Returns(new Dictionary<object, object>());


            var httpContext = httpContextMoq.Object;




            var requestDelegate = new RequestDelegate(
                    (innerContext) => Task.FromResult(0));


            // Act
            var middleware = new SetRequestContextItems(
                    requestDelegate);
            await middleware.InvokeAsync(httpContext);


            // Assert


            // check if the HttpContext.Items dictionary
            // contains any Key with XRequestIdKey
            // which implies that the Middleware
            // was able to place a value inside the Items
            // dictionary which is the expectation


            Assert.True(
                httpContext.Items.ContainsKey(SetRequestContextItems.XRequestIdKey));
        }
    }
}

Similarly, for the other TestCase where the Request headers don’t contain the xRequestId header, we just pass an empty RequestHeaders instead of a Dictionary with a value inside it.

namespace ReadersApi.Tests
{
    public class SetRequestContextItemsTests
    {
        [Fact]
        public async Task CheckIfRequestIdContextItemIsSet_WhenRequestHeadersDontContainXRequestId()
        {
            // Arrange


            // Create an instance of SetRequestContextItems() class
            // Call the InvokeAsync() method
            // Check if the resultant HttpContext.Items Dictionary
            // contains xRequestIdKey with some Value


            // SetRequestContextItems() class requires two dependencies
            // HttpContext and RequestDelegate
            // We need to mock HttpContext.Request and RequestDelegate for our case
            // The Business Logic runs on HttpContext.Request.Headers Dictionary and
            // not any other HttpContext attribute,
            // so we can safely mock only the Request.Headers part
            // RequestDelegate is an ActionDelegate that invokes the next Task
            // Hence we can just pass a dummy Action as the next Task to perform


            // Mocking the HttpContext.Request.Headers when xRequestIdKey Header is not present
            var headers = new Dictionary<string, StringValues>();


            var httpContextMoq = new Mock<HttpContext>();
            httpContextMoq.Setup(x => x.Request.Headers)
                .Returns(new HeaderDictionary(headers));
            httpContextMoq.Setup(x => x.Items)
                .Returns(new Dictionary<object, object>());


            var httpContext = httpContextMoq.Object;




            var requestDelegate = new RequestDelegate(
                    (innerContext) => Task.FromResult(0));


            // Act
            var middleware = new SetRequestContextItems(
                    requestDelegate);
            await middleware.InvokeAsync(httpContext);


            // Assert


            // check if the HttpContext.Items dictionary
            // contains any Key with XRequestIdKey
            // which implies that the Middleware
            // was able to place a value inside the Items
            // dictionary which is the expectation


            Assert.True(
                httpContext.Items.ContainsKey(
                    SetRequestContextItems.XRequestIdKey));
        }
    }
}

Conclusion

When we run these Tests either by VisualStudio or by Dotnet CLI, we can see that our tests pass implying that the middleware works as expected.

dotnet test

Test run for ReadersApi.TestsbinDebugnetcoreapp3.1ReadersApi.Tests.dll(.NETCoreApp,Version=v3.1)
    Microsoft (R) Test Execution Command Line Tool Version 16.6.0
    Copyright (c) Microsoft Corporation.  All rights reserved.

    Starting test execution, please wait...

    A total of 1 test files matched the specified pattern.

    Test Run Successful.
    Total tests: 2
         Passed: 2
     Total time: 3.5400 Seconds

In this way, we can Unit Test a middleware component inside our ASP.NET Core applications, by contextually mocking the HttpContext and faking the RequestDelegate.


Buy Me A Coffee

Found this article helpful? Please consider supporting!

Ram
Ram

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity. You can connect with me on Medium, Twitter or LinkedIn.

Leave a Reply

Your email address will not be published. Required fields are marked *