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.
- Create Custom Middleware in ASP.NET Core
- Basic Knowledge in ASP.Net Core
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
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,
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
- .NET 8 Authentication with Identity in a Web API using Bearer Tokens and Cookies
- How to convert Text To Speech With Azure Cognitive Services using Angular and .Net Core
- CRUD operation using the repository pattern with .Net 8, Ef-Core, and MySQL
- How to use Response Compression in .NET Core
- How to Integrate GraphQL in .Net Core
- Upload Download and Delete files in Azure Blob Storage using ASP.NET Core and Angular
- How to upload files to Azure Blob Storage using Asp.Net Core Web API
- How to store app secrets in ASP.NET Core using Secret Manager
- Logging into Azure App Service with ASP.Net Core
- Integrate Paging in ASP.Net Core Web API- Beginner’s Guide
SUPPORT ME