xunit - how to get HttpContext.User.Identity in unit tests
Asked Answered
M

3

12

I added a method to my controllers to get the user-id from the JWT token in the HttpContext. In my unit tests the HttpContext is null, so I get an exception.

How can I solve the problem? Is there a way to moq the HttpContext?

Here is the method to get the user in my base controller

protected string GetUserId()
{
    if (HttpContext.User.Identity is ClaimsIdentity identity)
    {
        IEnumerable<Claim> claims = identity.Claims;
        return claims.ToList()[0].Value;
    }

    return "";
}

One of my tests look like this

[Theory]
[MemberData(nameof(TestCreateUsergroupItemData))]
public async Task TestPostUsergroupItem(Usergroup usergroup)
{
    // Arrange
    UsergroupController controller = new UsergroupController(context, mapper);

    // Act
    var controllerResult = await controller.Post(usergroup).ConfigureAwait(false);

    // Assert
    //....
}
Mccrary answered 20/11, 2019 at 9:16 Comment(0)
U
17

There really is no need to have to mock the HttpContext in this particular case.

Use the DefaultHttpContext and set the members necessary to exercise the test to completion

For example

[Theory]
[MemberData(nameof(TestCreateUsergroupItemData))]
public async Task TestPostUsergroupItem(Usergroup usergroup) {
    // Arrange

    //...

    var identity = new GenericIdentity("some name", "test");
    var contextUser = new ClaimsPrincipal(identity); //add claims as needed

    //...then set user and other required properties on the httpContext as needed
    var httpContext = new DefaultHttpContext() {
        User = contextUser;
    };

    //Controller needs a controller context to access HttpContext
    var controllerContext = new ControllerContext() {
        HttpContext = httpContext,
    };
    //assign context to controller
    UsergroupController controller = new UsergroupController(context, mapper) {
        ControllerContext = controllerContext,
    };

    // Act
    var controllerResult = await controller.Post(usergroup).ConfigureAwait(false);

    // Assert
    ....
}
Unseen answered 20/11, 2019 at 9:52 Comment(0)
G
7

First of all, I would suggest you to use IHttpContextAccessor to access HttpContext and inject via Dependency Injection instead of using HttpContext directly. You can follow this Microsoft documentation to understand usage and injection of IHttpContextAccessor.

With the above code, your code will looks as follows to inject IHttpContextAccessor

private IHttpContextAccessor  httpContextAccessor;
public class UsergroupController(IHttpContextAccessor httpContextAccessor, ...additional parameters)
{
   this.httpContextAccessor = httpContextAccessor;
   //...additional assignments
}

Once IHttpContextAccessor is injected, you can access the Identity as this.httpContextAccessor.HttpContext.User.Identity

So the GetUserId should change as

protected string GetUserId()
{
    if (this.httpContextAccessor.HttpContext.User.Identity is ClaimsIdentity identity)
    {
        IEnumerable<Claim> claims = identity.Claims;
        return claims.ToList()[0].Value;
    }

    return "";
}

With above change, now you can easily inject the mock of IHttpContextAccessor for unit testing. You can use the below code to create the mock:

private static ClaimsPrincipal user = new ClaimsPrincipal(
                        new ClaimsIdentity(
                            new Claim[] { new Claim("MyClaim", "MyClaimValue") },
                            "Basic")
                        );


private static Mock<IHttpContextAccessor> GetHttpContextAccessor()
{
        var httpContextAccessorMock = new Mock<IHttpContextAccessor>();
        httpContextAccessorMock.Setup(h => h.HttpContext.User).Returns(user);
        return httpContextAccessorMock;
}

With the above setup, in your test method, you can inject the mock of IHttpContextAccessor while instantiating the object of UsergroupController.

Griskin answered 20/11, 2019 at 9:36 Comment(1)
The controller already has access to HttpContext. IHttpContextAccessor was meant to be used outside of a controller.Unseen
O
3

As a follow up to the comment by @Nkosi: there's no requirement to DI the context as you can set it during testing like so:

var identity = new GenericIdentity("some name", "test");
var contextUser = new ClaimsPrincipal(identity); 
controller.ControllerContext = new ControllerContext
{
    HttpContext = new DefaultHttpContext { User = contextUser }
};
Obvolute answered 20/5, 2022 at 8:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.