13

Lets say I am setting a value on the http context in my middleware. For example HttpContext.User.

How can test the http context in my unit test. Here is an example of what I am trying to do

Middleware

public class MyAuthMiddleware
{
    private readonly RequestDelegate _next;

    public MyAuthMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        context.User = SetUser(); 
        await next(context);
    }
}

Test

[Fact]
public async Task UserShouldBeAuthenticated()
{
    var server = TestServer.Create((app) => 
    {
        app.UseMiddleware<MyAuthMiddleware>();
    });

    using(server)
    {
        var response = await server.CreateClient().GetAsync("/");
        // After calling the middleware I want to assert that 
        // the user in the HttpContext was set correctly
        // but how can I access the HttpContext here?
    }
}
Sul Aga
  • 6,142
  • 5
  • 25
  • 37

4 Answers4

13

Following are two approaches you could use:

// Directly test the middleware itself without setting up the pipeline
[Fact]
public async Task Approach1()
{
    // Arrange
    var httpContext = new DefaultHttpContext();
    var authMiddleware = new MyAuthMiddleware(next: (innerHttpContext) => Task.FromResult(0));

    // Act
    await authMiddleware.Invoke(httpContext);

    // Assert
    // Note that the User property on DefaultHttpContext is never null and so do
    // specific checks for the contents of the principal (ex: claims)
    Assert.NotNull(httpContext.User);
    var claims = httpContext.User.Claims;
    //todo: verify the claims
}

[Fact]
public async Task Approach2()
{
    // Arrange
    var server = TestServer.Create((app) =>
    {
        app.UseMiddleware<MyAuthMiddleware>();

        app.Run(async (httpContext) =>
        {
            if(httpContext.User != null)
            {
                await httpContext.Response.WriteAsync("Claims: "
                    + string.Join(
                        ",",
                        httpContext.User.Claims.Select(claim => string.Format("{0}:{1}", claim.Type, claim.Value))));
            }
        });
    });

    using (server)
    {
        // Act
        var response = await server.CreateClient().GetAsync("/");

        // Assert
        var actual = await response.Content.ReadAsStringAsync();
        Assert.Equal("Claims: ClaimType1:ClaimType1-value", actual);
    }
}
Kiran
  • 56,921
  • 15
  • 176
  • 161
  • I can see that in the 2nd approach you used a middleware to write to the response which is really smart. Having said that, there no other way to get the http context in the 2nd approach from lets say IHttpContextAccessor? – Sul Aga May 31 '15 at 19:07
  • 1
    @SulAga : I would say that the 2nd approach is really an integration test and not an unit test. Regarding your question, the context is being supplied directly to the middleware's Invoke method here so you need not use IHttpContextAccessor. – Kiran Jun 01 '15 at 20:09
  • I am using asp.net rtm. When I try to use approach 1 I get a null reference exception somewhere deep inside aspnet.corelib. Any thoughts? – pthalacker Aug 08 '16 at 23:21
8

The RC1 version of asp.net 5/MVC6 makes it possible to set HttpContext manually in Unit Tests, which is awesome!

        DemoController demoController = new DemoController();
        demoController.ActionContext = new ActionContext();
        demoController.ActionContext.HttpContext = new DefaultHttpContext();
        demoController.HttpContext.Session = new DummySession();

DefaultHttpContext class is provided by the platform. DummySession can be just simple class that implements ISession class. This simplifies things a lot, because no more mocking is required.

Tuukka Lindroos
  • 1,270
  • 10
  • 14
  • 1
    `HttpContext` has been renamed to `ControllerContext`. But your answer was useful to achieve something related. – thoean Nov 22 '16 at 09:04
3

It would be better if you unit test your middleware class in isolation from the rest of your code.

Since HttpContext class is an abstract class, you can use a mocking framework like Moq (adding "Moq": "4.2.1502.911", as a dependency to your project.json file) to verify that the user property was set.

For example you can write the following test that verifies your middleware Invoke function is setting the User property in the httpContext and calling the next middleware:

[Fact]
public void MyAuthMiddleware_SetsUserAndCallsNextDelegate()
{
    //Arrange
    var httpContextMock = new Mock<HttpContext>()
            .SetupAllProperties();
    var delegateMock = new Mock<RequestDelegate>();
    var sut = new MyAuthMiddleware(delegateMock.Object);

    //Act
    sut.Invoke(httpContextMock.Object).Wait();

    //Assert
    httpContextMock.VerifySet(c => c.User = It.IsAny<ClaimsPrincipal>(), Times.Once);
    delegateMock.Verify(next => next(httpContextMock.Object), Times.Once);
}

You could then write additional tests for verifying the user has the expected values, since you will be able to get the setted User object with httpContextMock.Object.User:

Assert.NotNull(httpContextMock.Object.User);
//additional validation, like user claims, id, name, roles
Daniel J.G.
  • 34,266
  • 9
  • 112
  • 112
  • Just FYI...using Moq is also fine but it currently does not work on core clr. – Kiran May 31 '15 at 14:26
  • Good to know! So if you want to target the core clr you will either need to use DefaultHttpContext as in your example or create manual mock classes extending HttpContext. – Daniel J.G. May 31 '15 at 14:33
  • Yeah..right, although its preferred to use DefaultHttpContext as it was designed with unit testability in mind. – Kiran May 31 '15 at 14:36
2

take a look at this post:

Setting HttpContext.Current.Session in a unit test

I think what you need is this.

public static HttpContext FakeHttpContext(string url)
{
    var uri = new Uri(url);
    var httpRequest = new HttpRequest(string.Empty, uri.ToString(),
                                        uri.Query.TrimStart('?'));
    var stringWriter = new StringWriter();
    var httpResponse = new HttpResponse(stringWriter);
    var httpContext = new HttpContext(httpRequest, httpResponse);

    var sessionContainer = new HttpSessionStateContainer("id",
                                    new SessionStateItemCollection(),
                                    new HttpStaticObjectsCollection(),
                                    10, true, HttpCookieMode.AutoDetect,
                                    SessionStateMode.InProc, false);

    SessionStateUtility.AddHttpSessionStateToContext(
                                         httpContext, sessionContainer);

    return httpContext;
}

Then you can use it like:

request.SetupGet(req => req.Headers).Returns(new NameValueCollection());
HttpContextFactory.Current.Request.Headers.Add(key, value);
Community
  • 1
  • 1
  • 3
    This doesn´t apply to ASP.Net 5/MVC 6, which has a [new HttpContext model](http://gunnarpeipman.com/2014/07/asp-net-5-new-httpcontext/) – Daniel J.G. May 31 '15 at 13:11