Menu Close

Implement Unit Test in ASP.Net Core Middleware

In this article we will learn about how to Implement Unit Test in ASP.Net Core Middleware. Each ASP.NET Core request processing pipeline contains one or more middleware components. In this post, we’ll look at how to use unit tests to test middleware components and classes. Please read my previous article of How to Implement Repository Pattern in ASP.Net Core Web API.

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.

We will begin by developing an ASP.NET Core-based application that includes a math operation service and will use xUnit .Net to automate testing of the operations available in this service.

Find Source Code on the GitHub link

Prerequisites

You just need to have .NET Core installed on your machine if you want to follow the steps. You can create one ASP .NET Core web application.

Why Unit Test Middleware?

Many of us believe that the ASP.NET Core layer should not be unit tested in most circumstances since it is difficult to mimic all of the dependencies and, in most cases, it is not worth it.

In some circumstances, the middleware may be so simple and uncomplicated (such as managing static files) that you do not need to unit test it. In other circumstances, the middleware may be carrying out some rather extensive functionality (for example, handling unhandled exceptions or logging requests). In such circumstances, you should unit test the logic.

The decision must be carefully considered. If unit testing is not done appropriately, it may wind up taking a lot of time instead of giving you with insights and assistance.

Create ASP.Net Core Web API project and implement Unit Test

Let’s first create an ASP.Net Core Web API Project and on the project create a custom middleware and will create a Unit Test class library project to test the custom middleware.

  • Launch the Visual Studio IDE and click on “Create new project”.
  • In the “Create new project” window, select “ASP.NET Core Web API Project” from the list of templates displayed.
  • Click Next. In the “Configure your new project” window, specify the name and location for the new project and then click Create.
  • In the “Create New ASP.NET Core API” window shown next, select .NET Core as the runtime and .NET 7.0 from the drop-down list at the top. Select “API” as the project template to create a new ASP.NET Core API application. 
  • Ensure that the check boxes “Enable Docker Support” is disabled s we won’t be using the default feature we will add it manually later and “Configure for HTTPS” are checked.
  • Ensure that Authentication is set as “No Authentication” as we won’t be using authentication either and Click Create.

Create a folder name as “Middleware” and then create a middleware file (Right Click on the folder ⟷ Add New Item ⟷ Search Middleware and named as SetRequestContextItemsMiddleware.cs,

To demonstrate this, consider a simple Middleware component within our ReadersAPI that searches the incoming Request Headers for a “X-Request-ID” header that uniquely identifies a request and stores it in the HttpContext.Items dictionary. If the inbound Request Headers do not include this header, the Middleware constructs a Guid to represent a RequestId and stores it in the Items dictionary.

To begin, consider HttpContext.Items is a dictionary that we may use to store data that endures between many Requests. It is a useful data structure for passing persistent data between components within a single Request scope.

We set the “X-Request-ID” header value within our HttpContext.Items and utilise it to aggregate all of the Logs recorded for this specific Request in our Logging framework, which is outside the scope of our aim.

Because this exercise occurs at the Request level, we may utilize a Middleware to do this task for every incoming request before it reaches its appropriate handler.

namespace MiddlewareUnitTest.Middleware
{
    // You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
    public class SetRequestContextItemsMiddleware
    {
        private readonly RequestDelegate _next;
        public const string XRequestIdKey = "X-Request-ID";

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

        public async Task InvokeAsync(HttpContext httpContext)
        {
            // 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 = httpContext.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();
            }

            httpContext.Items.Add(XRequestIdKey, xRequestIdValue);

            await _next.Invoke(httpContext);
        }
    }

    // Extension method used to add the middleware to the HTTP request pipeline.
    public static class CustomMiddlewareExtensions
    {
        public static IApplicationBuilder UseCustomMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<SetRequestContextItemsMiddleware>();
        }
    }
}

Register this middleware component into our Request pipeline as below.

using MiddlewareUnitTest.Middleware;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseMiddleware<SetRequestContextItemsMiddleware>();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Create an controller ContextDetailsController and add a Test Endpoint that returns all the contents of HttpContext.Items dictionary for every request.

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

Run the API and you can see it register the X-Request-Id with the new GUID

X-Request-Id Swagger Get

Using xUnit to write Unit Tests

To develop unit tests individually, let’s construct the xUnit class library project. We have create xUnit project named as “MiddlewareTest”

How to Mock Middleware dependencies for Unit Testing

Writing Unit Tests is straightforward: we use the 3A principle – Arrange, Act, Assert – to build an instance of the unit we’re testing with unreal dependents if necessary; and test how its own logic works in perfect settings regardless of its dependencies.

We must consider two dependencies when developing Middleware:

  • HttpContext – the incoming Request Context.
  • RequestDelegate – indicates the next Action in the pipeline that must be executed once the current Middleware is finished. The RequestDelegate instance must be passed to our Middleware constructor, and the HttpContext must be passed as a parameter to the InvokeAsync() method.

Because we’re doing a UnitTest on the Middleware class as if it were any other concrete class, a genuine HttpContext, as well as the RequestDelegate, cannot be brought in. As a result, we’ll provide Mock dependencies to our Middleware class to get things done.

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;

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;

Notice how we’re also configuring the Items dictionary so that when the Middleware tries to insert a value into the dictionary, it won’t be acting on a null property, which is the default for any unconfigured property in the Mocked object.

So we send an empty dictionary so that we can later check to see if any values have been added to it.

How to Fake RequestDelegate of Middleware

As previously stated, RequestDelegate represents the next Middleware that must be triggered in the Request pipeline. We may just Fake it with an empty Action that performs nothing because we don’t need to launch any other Task later in our test.

We just build a new RequestDelegate object and pass it an action that returns a finished task with no error code.

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

Final Code like below,

 [Fact]
        public async Task WhenRequestHeadersContainXRequestId_IfContextItemIsSet_True()
        {
            // 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>() {
            { SetRequestContextItemsMiddleware.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 SetRequestContextItemsMiddleware(
                    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(SetRequestContextItemsMiddleware.XRequestIdKey));
        }

Similarly, in the other TestCase when the Request headers lack the xRequestId header, we simply pass an empty RequestHeaders rather than a Dictionary with a value inside it.

 [Fact]
        public async Task WhenRequestHeadersDoesNotContainXRequestId_IfContextItemIsNotSet_True()
        {
            //var headers = new Dictionary<string, StringValues>() {
            //{ SetRequestContextItemsMiddleware.XRequestIdKey, "123456" }};

            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 SetRequestContextItemsMiddleware(
                    requestDelegate);
            await middleware.InvokeAsync(httpContext);

            Assert.True(
                 httpContext.Items.ContainsKey(
                     SetRequestContextItemsMiddleware.XRequestIdKey));

        }

Let’s run the Unit Test for both of the methods, you can see the result output like below,

middleware-unit-test-aspnetcore

Source Code

Find Source Code on the GitHub link

Conclusion

In this article we discussed about how to Implement Unit Test in ASP.Net Core Middleware. Each ASP.NET Core request processing pipeline contains one or more middleware components. Here we have create a middleware and create the class library of xUnit and test the middleware.

Leave behind your valuable queries and suggestions in the comment section below. Also, if you think this article helps you, do not forget to share this with your developer community. Happy Coding 🙂

Related Post


SUPPORT ME

Leave a Reply

Your email address will not be published.