Middleware
plays a crucial role in the request processing pipeline of an ASP.NET Core application. It provides a way to handle cross-cutting concerns, such as logging, exception handling, and authentication, among others. To ensure the reliability and correctness of your middleware components, it’s important to write comprehensive unit tests. In this article, we’ll explore how to write unit tests for middleware in ASP.NET Core using a practical example.
Example Middleware: ExceptionHandlerMiddleware
Let’s consider a sample middleware called ExceptionHandlerMiddleware
that catches exceptions and returns an error response to the client. This middleware provides a centralized mechanism to handle exceptions and maintain a consistent error response format. Here’s the implementation of the ExceptionHandlerMiddleware
:
// ExceptionHandlerMiddleware.cs
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Net;
using System.Threading.Tasks;
namespace YourNamespace
{
public class ExceptionHandlerMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public ExceptionHandlerMiddleware(RequestDelegate next, ILogger<ExceptionHandlerMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError($"Something went wrong: {ex}");
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync("Internal Server Error.");
}
}
}
}
The ExceptionHandlerMiddleware
catches exceptions thrown by subsequent middleware or the request handler and returns an error response with a 500 status code and a plain text message.
Writing Unit Tests for ExceptionHandlerMiddleware
To ensure the correctness of the ExceptionHandlerMiddleware
, we need to write unit tests that cover both the exception handling scenario and the scenario where no exception is thrown. Let’s explore how to write these unit tests using the Xunit testing framework.
[Fact]
public async Task Invoke_ExceptionCaught_ReturnsInternalServerError()
{
// Arrange
var context = new DefaultHttpContext();
context.Response.Body = new MemoryStream();
context.Request.Path = "/";
var expectedOutput = "Internal Server Error.";
var middleware = new ExceptionHandlerMiddleware((innerHttpContext) =>
{
throw new Exception("Something went wrong");
}, Mock.Of<ILogger<ExceptionHandlerMiddleware>>());
// Act
await middleware.Invoke(context);
// Assert
context.Response.Body.Seek(0, SeekOrigin.Begin);
var body = await new StreamReader(context.Response.Body).ReadToEndAsync();
Assert.Contains(expectedOutput, body);
Assert.Equal(StatusCodes.Status500InternalServerError, context.Response.StatusCode);
Assert.Equal("application/json", context.Response.ContentType);
}
In this unit test:
-
[Fact]
: The[Fact]
attribute indicates that this is a unit test method. -
Invoke_ExceptionCaught_ReturnsInternalServerError()
: The method represents a test case where an exception is thrown by the middleware. -
Arrange:
- We create an instance of
DefaultHttpContext
to simulate an HTTP context for the test. - The response body of the context is set to a
MemoryStream
to capture the response content. - The
context.Request.Path
is set to"/"
to simulate the requested path. - The
expectedOutput
variable holds the expected error message that should be returned by the middleware. - We create an instance of the
ExceptionHandlerMiddleware
, passing in a delegate that throws an exception and a mock logger.
- We create an instance of
-
Act:
- The
await middleware.Invoke(context)
line invokes the middleware, simulating the request processing.
- The
-
Assert:
- We reset the position of the response body stream to the beginning to read the response content.
- The
StreamReader
is used to read the response body into a string variable namedbody
. Assert.Contains(expectedOutput, body)
verifies that the expected error message is present in the response body.Assert.Equal(StatusCodes.Status500InternalServerError, context.Response.StatusCode)
ensures that the response status code is set to 500 (Internal Server Error).Assert.Equal("application/json", context.Response.ContentType)
verifies that the response content type is set to “application/json”.
In summary, this unit test sets up the necessary context for the middleware, triggers the middleware invocation, and then verifies the expected behavior of the middleware by examining the response.