136

I have an ASP.NET MVC Core application that I am writing unit tests for. One of the action methods uses User name for some functionality:

SettingsViewModel svm = _context.MySettings(User.Identity.Name);

which obviously fails in the unit test. I looked around and all suggestions are from .NET 4.5 to mock HttpContext. I am sure there is a better way to do that. I tried to inject IPrincipal, but it threw an error; and I even tried this (out of desperation, I suppose):

public IActionResult Index(IPrincipal principal = null) {
    IPrincipal user = principal ?? User;
    SettingsViewModel svm = _context.MySettings(user.Identity.Name);
    return View(svm);
}

but this threw an error as well. Couldn't find anything in the docs either...

Felix
  • 9,248
  • 10
  • 57
  • 89

8 Answers8

261

The controller’s User is accessed through the HttpContext of the controller. The latter is stored within the ControllerContext.

The easiest way to set the user is by assigning a different HttpContext with a constructed user. We can use DefaultHttpContext for this purpose, that way we don’t have to mock everything. Then we just use that HttpContext within a controller context and pass that to the controller instance:

var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
{
    new Claim(ClaimTypes.Name, "example name"),
    new Claim(ClaimTypes.NameIdentifier, "1"),
    new Claim("custom-claim", "example claim value"),
}, "mock"));

var controller = new SomeController(dependencies…);
controller.ControllerContext = new ControllerContext()
{
    HttpContext = new DefaultHttpContext() { User = user }
};

When creating your own ClaimsIdentity, make sure to pass an explicit authenticationType to the constructor. This makes sure that IsAuthenticated will work correctly (in case you use that in your code to determine whether a user is authenticated).

poke
  • 369,085
  • 72
  • 557
  • 602
  • 8
    In my case it was `new Claim(ClaimTypes.Name, "1")` to match controller use of `user.Identity.Name`; but otherwise it's exactly what I was trying to achieve... Danke schon! – Felix Jul 25 '16 at 07:35
  • After countless hours searching this was the post that finally got me squared away. In my core 2.0 project controller method I was utilizing `User.FindFirstValue(ClaimTypes.NameIdentifier);` to set the userId on an object I was creating and was failing because principal was null. This fixed that for me. Thanks for the great answer! – Timothy Randall Dec 01 '17 at 05:27
  • I was also searching countless hours to get UserManager.GetUserAsync to work and this is the only place I found the missing link. Thanks! Need to setup a ClaimsIdentity containing a Claim, not use a GenericIdentity. – Etienne Charland Apr 12 '19 at 04:23
  • how do you make a Claims that contains a list of strings for example, I cant find anything like this anywhere. This answer helped a lot btw, but I have this problem now, because I need to mock a claim with roles that my `User ` should have – Javier Buffa Oct 01 '21 at 21:59
  • Well rather than mocking, actually I'm creating a fake Identity object just like the above example – Javier Buffa Oct 01 '21 at 22:05
  • 1
    @JavierBuffa Single claims do not support multiple values; instead, there will be multiple claims that have the same type. So the array passed to the ClaimsIdentity constructor can just have multiple role claims, e.g. `new Claim(ClaimTypes.Role, "role1"), new Claim(ClaimTypes.Role, "role2")`. – poke Oct 01 '21 at 23:39
  • @poke Where does this code go? Within another controllers constructor, in the startup class or somewhere else? – Dean P Oct 25 '21 at 13:30
  • 1
    @DeanP This assumes that you are in a unit test where you are creating a controllere (here: `SomeController`) and you want to instantiate that controller and then execute a controller action on it as part of your unit test. – You usually do not want to do this in your actual application code. – poke Oct 29 '21 at 14:02
21

In previous versions you could have set User directly on the controller, which made for some very easy unit tests.

If you look at the source code for ControllerBase you will notice that the User is extracted from HttpContext.

/// <summary>
/// Gets the <see cref="ClaimsPrincipal"/> for user associated with the executing action.
/// </summary>
public ClaimsPrincipal User => HttpContext?.User;

and the controller accesses the HttpContext via ControllerContext

/// <summary>
/// Gets the <see cref="Http.HttpContext"/> for the executing action.
/// </summary>
public HttpContext HttpContext => ControllerContext.HttpContext;

You will notice that these two are read only properties. The good news is that ControllerContext property allows for setting it's value so that will be your way in.

So the target is to get at that object. In Core HttpContext is abstract so it is a lot easier to mock.

Assuming a controller like

public class MyController : Controller {
    IMyContext _context;

    public MyController(IMyContext context) {
        _context = context;
    }

    public IActionResult Index() {
        SettingsViewModel svm = _context.MySettings(User.Identity.Name);
        return View(svm);
    }

    //...other code removed for brevity 
}

Using Moq, a test could look like this

public void Given_User_Index_Should_Return_ViewResult_With_Model() {
    //Arrange 
    var username = "FakeUserName";
    var identity = new GenericIdentity(username, "");

    var mockPrincipal = new Mock<ClaimsPrincipal>();
    mockPrincipal.Setup(x => x.Identity).Returns(identity);
    mockPrincipal.Setup(x => x.IsInRole(It.IsAny<string>())).Returns(true);

    var mockHttpContext = new Mock<HttpContext>();
    mockHttpContext.Setup(m => m.User).Returns(mockPrincipal.Object);

    var model = new SettingsViewModel() {
        //...other code removed for brevity
    };

    var mockContext = new Mock<IMyContext>();
    mockContext.Setup(m => m.MySettings(username)).Returns(model);

    var controller = new MyController(mockContext.Object) {
        ControllerContext = new ControllerContext {
            HttpContext = mockHttpContext.Object
        }
    };

    //Act
    var viewResult = controller.Index() as ViewResult;

    //Assert
    Assert.IsNotNull(viewResult);
    Assert.IsNotNull(viewResult.Model);
    Assert.AreEqual(model, viewResult.Model);
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
5

There is also the possibility to use the existing classes, and mock only when needed.

var user = new Mock<ClaimsPrincipal>();
_controller.ControllerContext = new ControllerContext
{
    HttpContext = new DefaultHttpContext
    {
        User = user.Object
    }
};
Nkosi
  • 235,767
  • 35
  • 427
  • 472
Calin
  • 6,661
  • 7
  • 49
  • 80
4

In my case, I needed to make use of Request.HttpContext.User.Identity.IsAuthenticated, Request.HttpContext.User.Identity.Name and some business logic sitting outside of the controller. I was able to use a combination of Nkosi's, Calin's and Poke's answer for this:

var identity = new Mock<IIdentity>();
identity.SetupGet(i => i.IsAuthenticated).Returns(true);
identity.SetupGet(i => i.Name).Returns("FakeUserName");

var mockPrincipal = new Mock<ClaimsPrincipal>();
mockPrincipal.Setup(x => x.Identity).Returns(identity.Object);

var mockAuthHandler = new Mock<ICustomAuthorizationHandler>();
mockAuthHandler.Setup(x => x.CustomAuth(It.IsAny<ClaimsPrincipal>(), ...)).Returns(true).Verifiable();

var controller = new MyController(...);

var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(m => m.User).Returns(mockPrincipal.Object);

controller.ControllerContext = new ControllerContext();
controller.ControllerContext.HttpContext = new DefaultHttpContext()
{
    User = mockPrincipal.Object
};

var result = controller.Get() as OkObjectResult;
//Assert results

mockAuthHandler.Verify();
Luke
  • 776
  • 9
  • 24
2

I want to hit my Controllers directly and just use DI like AutoFac. To do this I first registering ContextController.

var identity = new GenericIdentity("Test User");
var httpContext = new DefaultHttpContext()
{
    User = new GenericPrincipal(identity, null)
};

var context = new ControllerContext { HttpContext = httpContext};
builder.RegisterInstance(context);

Next I enable property injection when I register the Controllers.

  builder.RegisterAssemblyTypes(assembly)
                    .Where(t => t.Name.EndsWith("Controller")).PropertiesAutowired();

Then User.Identity.Name is populated, and I do not need to do anything special when calling a method on my Controller.

public async Task<ActionResult<IEnumerable<Employee>>> Get()
{
    var requestedBy = User.Identity?.Name;
    ..................
Devgig
  • 141
  • 1
  • 3
1

I would look to implement an Abstract Factory Pattern.

Create an interface for a factory specifically for providing user names.

Then provide concrete classes, one which provides User.Identity.Name, and one that provides some other hard coded value that works for your tests.

You can then use the appropriate concrete class depending on production versus test code. Perhaps looking to pass the factory in as a parameter, or switching to the correct factory based on some configuration value.

interface IUserNameFactory
{
    string BuildUserName();
}

class ProductionFactory : IUserNameFactory
{
    public BuildUserName() { return User.Identity.Name; }
}

class MockFactory : IUserNameFactory
{
    public BuildUserName() { return "James"; }
}

IUserNameFactory factory;

if(inProductionMode)
{
    factory = new ProductionFactory();
}
else
{
    factory = new MockFactory();
}

SettingsViewModel svm = _context.MySettings(factory.BuildUserName());
James Wood
  • 17,286
  • 4
  • 46
  • 89
  • Thank you. I am doing something similar for *my* objects. I was just hoping that for such common thing as IPrinicpal, there would be something "out of the box". But apparently, not! – Felix Jul 25 '16 at 00:09
  • Additionally, User is member variable of ControllerBase. That's why in earlier versions of ASP.NET people mocked HttpContext, and were getting IPrincipal from there. One can't just get User from a standalone class, like ProductionFactory – Felix Jul 25 '16 at 00:19
1

I got a brownfield .net 4.8 project that I needed to convert to .net 5.0 and I wanted to keep as much of the original code as possible, including the unit-/integration tests. The test for Controllers relied on the Context a lot so I created this Extension method to enable setting tokens, claims and headers:

public static void AddContextMock(
    this ControllerBase controller,
    IEnumerable<(string key, string value)> claims = null,
    IEnumerable<(string key, string value)> tokens = null,
    IEnumerable<(string key, string value)> headers = null)
{
    HttpContext mockContext = new DefaultHttpContext();
    if(claims != null)
    {
        mockContext.User = SetupClaims(claims);
    }
    if(tokens != null)
    {
        mockContext.RequestServices = SetupTokens(tokens);
    }
    if(headers != null)
    {
        SetupHeaders(mockContext, headers);
    }

    controller.ControllerContext = new ControllerContext()
    {
        HttpContext = mockContext
    };
}

private static void SetupHeaders(HttpContext mockContext, IEnumerable<(string key, string value)> headers)
{
    foreach(var header in headers)
    {
        mockContext.Request.Headers.Add(header.key, header.value);
    }
}

private static ClaimsPrincipal SetupClaims(IEnumerable<(string key, string value)> claimValues)
{
    var claims = claimValues.Select(c => new Claim(c.key, c.value));
    return new ClaimsPrincipal(new ClaimsIdentity(claims, "mock"));
}

private static IServiceProvider SetupTokens(IEnumerable<(string key, string value)> tokenValues)
{
    var mockServiceProvider = new Mock<IServiceProvider>();
    var authenticationServiceMock = new Mock<IAuthenticationService>();
    var authResult = AuthenticateResult.Success(
        new AuthenticationTicket(new ClaimsPrincipal(), null));
    var tokens = tokenValues.Select(t => new AuthenticationToken { Name = t.key, Value = t.value });
    authResult.Properties.StoreTokens(tokens);

    authenticationServiceMock
        .Setup(x => x.AuthenticateAsync(It.IsAny<HttpContext>(), null))
        .ReturnsAsync(authResult);

    mockServiceProvider.Setup(_ => _.GetService(typeof(IAuthenticationService))).Returns(authenticationServiceMock.Object);
    return mockServiceProvider.Object;
}

This uses Moq but can be adapted to other mocking frameworks. The authentication type is hardcoded to "mock" since I rely on default authentication but this could be supplied as well.

It is used as such:

_controllerUnderTest.AddContextMock(
    claims: new[]
    {
        (ClaimTypes.Name, "UserName"),
        (ClaimTypes.MobilePhone, "1234"),
    },
    tokens: new[] 
    { 
        ("access_token", "accessTokenValue") 
    },
    headers: new[]
    {
        ("header", "headerValue")
    });
Klepto
  • 718
  • 1
  • 7
  • 14
0

If you're using Razor pages and want to override the claims:

[SetUp]
public void Setup()
{
    var user = new ClaimsPrincipal(new ClaimsIdentity(
    new Claim[] { 
        new("dateofbirth", "2000-10-10"),
        new("surname", "Smith") },
    "mock"));

    _razorModel = new RazorModel()
    {
        PageContext = new PageContext
        {
            HttpContext = new DefaultHttpContext() { User = user }
        }
    };
}
Luke Garrigan
  • 4,571
  • 1
  • 21
  • 29
  • I am not using Razor pages and not trying to override the claims. Please refer to the answers below for answers that are relevant to this question. Thank you – Felix Apr 23 '23 at 18:56