I came across a similar requirement today and I did not find any straightforward answer. I even tried the solution posted by Dai above, which was not working for me.
While going through the code, I noticed that OnStarting
is a virtual method part of the abstract HttpResponse class
that can be mocked. With that concept, I was able to find a working solution which I am posting here:
Sample Middleware:
public class ApiContextMiddleware
{
private readonly RequestDelegate _next;
public ApiContextMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
context.Response.OnStarting(() =>
{
context.Response.Headers.Add("NetVersion", "VersionSix");
return Task.CompletedTask;
});
await _next(context);
}
}
For this solution, we need to mock HttpContext
and HttpResponse
as some properties of HttpContext
are read-only (get
only).
Mock<HttpContext> httpContextMock = new();
Mock<HttpResponse> httpResponseMock = new();
Now create a Func<Task>
variable to hold your callback method and mock the OnStarting
method of the httpResponseMock
mock object like below.
Func<Task>? callbackMethod = null;
httpResponseMock.Setup(x =>
x.OnStarting(It.IsAny<Func<Task>>()))
.Callback<Func<Task>>(m => callbackMethod = m);
Next, mock HttpResponse
and HttpResponse.Headers
of the httpContextMock
mock object to return httpResponseMock.Object
and a HeaderDictionary
object, respectively.
httpContextMock.SetupGet(x => x.Response)
.Returns(httpResponseMock.Object);
httpContextMock.SetupGet(x => x.Response.Headers)
.Returns(new HeaderDictionary());
Now, create a RequestDelegate
method, which is required for the middleware class, where we will be invoking the OnStarting
Method.
var requestDelegate = new RequestDelegate(async (innerContext) =>
{
isNextDelegateCalled = true;
if (callbackMethod != null)
{
await callbackMethod.Invoke();
}
else
{
await Task.CompletedTask;
}
});
That's it. Now you will be able to assert the response header key and value, added by your OnStarting
callback method.
Here is the full test method:
[Fact]
public async Task ApiContextMiddleware_Should_Add_Header_Name_To_Http_Response()
{
bool isNextDelegateCalled = false;
Mock<HttpContext> httpContextMock = new();
Mock<HttpResponse> httpResponseMock = new();
Func<Task>? callbackMethod = null;
httpResponseMock.Setup(x =>
x.OnStarting(It.IsAny<Func<Task>>()))
.Callback<Func<Task>>(m => callbackMethod = m);
httpContextMock.SetupGet(x => x.Response)
.Returns(httpResponseMock.Object);
httpContextMock.SetupGet(x => x.Response.Headers)
.Returns(new HeaderDictionary());
var fakeHttpContext = httpContextMock.Object;
var requestDelegate = new RequestDelegate(async (innerContext) =>
{
isNextDelegateCalled = true;
if (callbackMethod != null)
{
await callbackMethod.Invoke();
}
else
{
await Task.CompletedTask;
}
});
var middelware = new ApiContextMiddleware(requestDelegate);
await middelware.InvokeAsync(fakeHttpContext);
Assert.True(isNextDelegateCalled);
Assert.True(fakeHttpContext.Response.Headers.TryGetValue("NetVersion", out var value));
Assert.Equal("VersionSix", value);
}
HttpContext
instance for testing - not because it instantiated a Controller. – Organometallic