How to Unit Test ASP .NET Core  Middleware ?
Unit testing ASP .NET Core Middleware

How to Unit Test ASP .NET Core Middleware ?

Every ASP .NET Core request processing pipeline has one or more than one middleware components. In this article, let’s have a look at how to test the middleware components / classes using unit tests.

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.

You can also refer my previous articles throwing more light on how to create custom middlewares.

Why unit test middleware?

Many of us have a view that the ASP .NET Core layer need not be unit tested in most of the cases as it is very hard to mock all the dependencies and in most of the cases, it may not be worth.

In some cases it might be true, in my opinion, there is no single universal answer to this question. It always has to be a pragmatic decision.

In some cases, the middleware might be very simple and straightforward enough (like handling static files), which you may not want to unit test. In some other cases, the middleware might be performing some fairly complex logic (e.g. handling unhandled exceptions, or logging requests). In those cases, you want to unit test the logic.

When you are in doubt, then also, you may want to write the unit tests. Why ? Because unit tests help future developers (or your future self) in understanding the requirements (if written carefully) and they provide quickest possible feedback to the code changes done by you.

The decision has to be thought thoroughly. If unit testing is not done carefully, instead of providing you insights and help, it may end up consuming a lot of time in maintaining those tests.

The Middleware

The middleware for this demo is simple and it is just adds a cookie DummyCookie to the response – if it is not already present in the request. It is not terminal middleware so after adding cookie to the response it just hands over response to the next middleware.

public class DummyCookieMiddleware
{
private readonly RequestDelegate _next;
public DummyCookieMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
// Check if cookie is already present
var dummyCookieValue = context.Request.Cookies["DummyCookie"];
var isValidDummyCookieValue = Guid.TryParse(dummyCookieValue, out Guid result);
// If not present then add new value
if(!isValidDummyCookieValue) {
context.Response.Cookies.Append("DummyCookie", Guid.NewGuid().ToString());
}
// handover request to next delegate in pipeline
return _next(context);
}
}

Create the middleware extension as shown below. The below method would be used to configure middleware in the request pipeline.

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

Now, after this, let’s configure this middleware in the request processing pipeline as shown below:

public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseDummyCookieMiddleware();
app.Run(async context =>
{
await context.Response.WriteAsync("App Response");
});
}
}
view raw Startup.cs hosted with ❤ by GitHub

Run the application and ensure that everything is working as expected.

The Test Proejct

The approach on high level is:

  • Mock all dependencies and Setup DI.
  • Instantiate the Middleware
    • context: pass DefaultHttpContext object to it
    • next: pass a dummy terminal middleware to it to end the response
  • Assert the changes in DefaultHttpContext object

Below are the command lines to create the xUnit test project and to add reference of web app project into the test project.

## Go to the directory which contains the web app project
cd D:\BLOG_Samples\FirstWebApp
## Navigate to parent directory of web app project directory
cd ..
## Create directory for test project
mkdir FirstWebApp.Tests
## Move to the test project directory
cd .\FirstWebApp.Tests\
## Create xUnit test proejct of same name (FirstWebApp.Tests)
dotnet new xunit
## Add reference of Web App project to the test project
dotnet add reference ..\FirstWebApp\FirstWebApp.csproj
## Move to the parent directory
cd ..
## Open Visual Studio Code
Code .
view raw commands.sh hosted with ❤ by GitHub

Let’s write the unit test

There should be at least two unit test to ensure that middleware is functioning as expected:

  • If incoming request does not have cookie, it would be added to response
  • If incoming request had the cookie, no cookie would be set in response

For this demo, we are going to have look at only first unit test. This unit test will have three important steps:

  • Arrange: It creates DefaultHttpContext object and sets few properties like path to some valid Request.Path and Response.Body is set to an empty memory stream
  • Act: It creates a new instance of middleware. A dummy delegate is passed to the middleware. This dummy delegate writes a string to the response. Then the middleware is invoked with instance of DefaultHttpContext object created in first step.
  • Assert: checks if the response has valid cookie and response body has the value written by the dummy request delegate.

Below is the code for the unit test:

public class DummyCookieMiddlewareTests
{
[Fact]
public void GivenWebApp_WhenFirstRequestIsSentToWebApp_ThenDummyCookieIsAddedToResponse()
{
const string expectedOutput = "Request handed over to next request delegate";
// Arrange
DefaultHttpContext defaultContext = new DefaultHttpContext();
defaultContext.Response.Body = new MemoryStream();
defaultContext.Request.Path = "/";
// Act
var middlewareInstance = new DummyCookieMiddleware(next: (innerHttpContext) =>
{
innerHttpContext.Response.WriteAsync(expectedOutput);
return Task.CompletedTask;
});
middlewareInstance.Invoke(defaultContext);
// Assert 1: if the next delegate was invoked
defaultContext.Response.Body.Seek(0, SeekOrigin.Begin);
var body = new StreamReader(defaultContext.Response.Body).ReadToEnd();
Assert.Equal(expectedOutput, body);
// Assert 2: if the cookie is added to the response
var setCookieHeaders = defaultContext.Response.GetTypedHeaders().SetCookie;
var cookie = setCookieHeaders?.FirstOrDefault(x => x.Name == "DummyCookie");
Assert.True(Guid.TryParse(cookie.Value, out Guid result));
}
}

I hope you found this information to be helpful. Let me know your thoughts.

Leave a Reply

This Post Has 2 Comments

  1. bertt

    Hi, there is no method UseDummyCookieMiddleware() in your sample, maybe it’s something like app.UseMiddleware(typeof(DummyCookieMiddleware)) ?

    1. Manoj Choudhari

      Updated post to describe how to add extension ‘UseDummyCookieMiddleware’. Thanks.