I have an ASP.NET Core middleware which is responsible for adding headers to a response. In following best practices, I am executing the header changes in the context of HttpResponse.OnStarting(Func<Task>)
, which ensures callback execution immediately before the response is flushed to the client.
public class ResponseHeadersMiddleware
{
private readonly RequestDelegate _next;
public ResponseHeadersMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
context.Response.OnStarting(() =>
{
context.Response.Headers.Add("X-Some-Header", "Foobar");
return Task.CompletedTask;
});
// Pass the request through the pipeline
await _next(context);
}
}
This works as-intended, but I am not sure how best to write a unit test for this middleware which actually fires HttpResponse.OnStarting()
. The only thing I could come up with was using a Microsoft.AspNetCore.TestHost
to build a TestServer
which integrates the middleware and executes the full request pipeline. While functional, this is more an integration test, than a true unit test.
[Fact]
public async Task when_adding_response_headers()
{
// ARRANGE
var subject = new TestServer(new WebHostBuilder()
.UseStartup<TestStartup<ResponseHeadersMiddleware>>());
// ACT
var response = await subject.CreateClient()
.SendAsync(new HttpRequestMessage(HttpMethod.Get, "/")); // middleware fires for all requests
// ASSERT
Assert.True(response.Headers.TryGetValues("X-Some-Header", out var someHeader));
Assert.Equals("Foobar", someHeader.FirstOrDefault()
}
private class TestStartup<TMiddleware> where TMiddleware : class
{
public void ConfigureServices(IServiceCollection services)
{
RequestDelegate requestDelegate = context => Task.FromResult(0);
services.AddSingleton(requestDelegate);
services.AddSingleton<TMiddleware>();
}
public void Configure(IApplicationBuilder app)
{
dynamic middleware = app.ApplicationServices.GetService(typeof(TMiddleware));
app.Use(async (ctx, next) =>
{
await middleware.Invoke(ctx);
await next();
});
}
}
Is there a way to trigger HttpResponse.OnStarting()
on the HttpContext passed to my middleware, without an end-to-end integration test?
After some digging around in the repository and looking at a few of their tests I came up with this idea to tap into the response feature of a controlled context.
That meant finding a way to capture the callback passed to OnStarting
. Decided to try and get it through a dummy response feature.
private class DummyResponseFeature : IHttpResponseFeature {
public Stream Body { get; set; }
public bool HasStarted { get { return hasStarted; } }
public IHeaderDictionary Headers { get; set; } = new HeaderDictionary();
public string ReasonPhrase { get; set; }
public int StatusCode { get; set; }
public void OnCompleted(Func<object, Task> callback, object state) {
//...No-op
}
public void OnStarting(Func<object, Task> callback, object state) {
this.callback = callback;
this.state = state;
}
bool hasStarted = false;
Func<object, Task> callback;
object state;
public Task InvokeCallBack() {
hasStarted = true;
return callback(state);
}
}
Within the test I would set the feature on the HttpContext
and then test the middle ware directly.
[Fact]
public async Task when_adding_response_headers() {
// ARRANGE
var feature = new DummyResponseFeature();
var context = new DefaultHttpContext();
context.Features.Set<IHttpResponseFeature>(feature);
RequestDelegate next = async (ctx) => {
await feature.InvokeCallBack();
};
var subject = new ResponseHeadersMiddleware(next);
// ACT
await subject.Invoke(context);
// ASSERT
var response = context.Response;
Assert.True(response.Headers.TryGetValues("X-Some-Header", out var someHeader));
Assert.Equals("Foobar", someHeader.FirstOrDefault()
}
having the request delegate invoke captured the callback when the request delegate is awaited in the middleware
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With