How do I mock User.Identity.GetUserId()?

22,760

Solution 1

You can't set up the GetUserId method because it's an extension method. Instead you'll have to set up the name on the user's IIdentity property. GetUserId will use this to determine the UserId.

var context = new Mock<HttpContextBase>();
var mockIdentity = new Mock<IIdentity>();
context.SetupGet(x => x.User.Identity).Returns(mockIdentity.Object);
mockIdentity.Setup(x => x.Name).Returns("test_name");

See this for more info: http://msdn.microsoft.com/en-us/library/microsoft.aspnet.identity.identityextensions.getuserid(v=vs.111).aspx

Solution 2

You can't mock extension methods directly, so your best bet is to drill down until you hit the properties and methods upon which the extension method depends that are mockable.

In this case, IIdentity.GetuUserId() is an extension method. I'd post it, but the library is not currently open source, so you'll have to see for yourself that GetUserId() depends on ClaimsIdentity.FindFirstValue(). As it turns out, that is also an extension method, but it depends on ClaimsIdentity.FindFirst(), which is marked virtual. Now we have our seam, so we can do the following:

var claim = new Claim("test", "IdOfYourChoosing");
var mockIdentity =
    Mock.Of<ClaimsIdentity>(ci => ci.FindFirst(It.IsAny<string>()) == claim);
var controller = new MyController()
{
    User = Mock.Of<IPrincipal>(ip => ip.Identity == mockIdentity)
};

controller.User.Identity.GetUserId(); //returns "IdOfYourChoosing"

Update: I just realized that the solution I posted above only works for derived ApiControllers (as in, Web API). The MVC Controller class doesn't have a settable User property. Fortunately it's pretty easy to go in through the Controller's ControllerContext to achieve the desired effect:

var claim = new Claim("test", "IdOfYourChoosing");
var mockIdentity =
    Mock.Of<ClaimsIdentity>(ci => ci.FindFirst(It.IsAny<string>()) == claim);
var mockContext = Mock.Of<ControllerContext>(cc => cc.HttpContext.User == mockIdentity);
var controller = new MyController()
{
    ControllerContext = mockContext
};

However, if all you're going to be using the User object for is to get the user's Id, there's another approach that will work for either type of controller and requires a lot less code:

public class MyController: Controller //or ApiController
{
    public Func<string> GetUserId; //For testing

    public MyController()
    {
        GetUserId = () => User.Identity.GetUserId();
    }
    //controller actions
}

Now instead of calling User.Identity.GetUserId() when you want the user's Id, you simply call GetUserId(). When you need to mock out the user Id in tests, you simply do it like this:

controller = new MyController()
{
    GetUserId = () => "IdOfYourChoosing"
};

There are pros and cons to both approaches. The first approach is more thorough and more flexible and uses seams that are already present on the two controller types, but it's also a lot more code and doesn't read nearly as well. The second approach is much cleaner and IMO more expressive, but it is extra code to maintain, and if you don't use the Func call to get the user's Id, your tests won't work. Choose whichever one works better for your situation.

Solution 3

This worked for me

using fakeiteasy

        var identity = new GenericIdentity("TestUsername");
        identity.AddClaim(new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", "UserIdIWantReturned"));

        var fakePrincipal = A.Fake<IPrincipal>();
        A.CallTo(() => fakePrincipal.Identity).Returns(identity);

        var _controller = new MyController
        {
            ControllerContext = A.Fake<ControllerContext>()
        };

        A.CallTo(() => _controller.ControllerContext.HttpContext.User).Returns(fakePrincipal); 

Now you can get values back from those IIdentity extention methods:

eg.

        var userid = _controller.User.Identity.GetUserId();
        var userName = _controller.User.Identity.GetUserName();

When viewing the GetUserId() method with ILSpy it shows

public static string GetUserId(this IIdentity identity)
{
  if (identity == null)
  {
    throw new ArgumentNullException("identity");
  }
  ClaimsIdentity claimsIdentity = identity as ClaimsIdentity;
  if (claimsIdentity != null)
  {
    return claimsIdentity.FindFirstValue(http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier);
  }
  return null;
}

So GetUserId needs the Claim "nameidentifier"

Share:
22,760
AndrewPolland
Author by

AndrewPolland

Updated on October 07, 2020

Comments

  • AndrewPolland
    AndrewPolland over 3 years

    I am trying to unit test my code which includes the line:

    UserLoginInfo userIdentity = UserManager.GetLogins(User.Identity.GetUserId()).FirstOrDefault();
    

    I'm just stuck on one bit as I can't get:

    User.Identity.GetUserId()
    

    to return a value. I have been trying the following in the set-up of my controller:

    var mock = new Mock<ControllerContext>();
    mock.Setup(p => p.HttpContext.User.Identity.GetUserId()).Returns("string");
    

    But it gives an error of "NotSupportedException was unhandled by user code". I have also tried the following:

    ControllerContext controllerContext = new ControllerContext();
    
    string username = "username";
    string userid = Guid.NewGuid().ToString("N"); //could be a constant
    
    List<Claim> claims = new List<Claim>{
        new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", username), 
        new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", userid)
    };
    var genericIdentity = new GenericIdentity("Andrew");
    genericIdentity.AddClaims(claims);
    var genericPrincipal = new GenericPrincipal(genericIdentity, new string[] { });
    controllerContext.HttpContext.User = genericPrincipal;
    

    Based on some code I found on stackoverflow, but this returns the same error "NotSupportedException was unhandled by user code".

    Any help as to how I proceed would be appreciated. Thanks.

  • AndrewPolland
    AndrewPolland about 10 years
    Turns out I was actually just doing something else stupid and this wasn't my issue at all. But your answer is correct. Thanks.
  • EF0
    EF0 almost 10 years
    +1 Configuring the call to ClaimsIdentity.FindFirst() solved my problem. Here is an example in FakeItEasy syntax for anyone using that: var fakeClaim = A.Fake<Claim>(x => x.WithArgumentsForConstructor(() => new Claim(ClaimTypes.NameIdentifier, FakeEmpIDString))); var fakeIdentity = A.Fake<ClaimsIdentity>(); A.CallTo(() => fakeIdentity.FindFirst(ClaimTypes.NameIdentifier)).Returns(f‌​akeClaim); My setup looks a little different; can't set Controller.User property directly so I inject a ControllerContext & set its HttpContext.User property to a ClaimsPrinciple object.
  • Jose A
    Jose A over 9 years
    Oof. The second worked flawlessly. Did this for grabbing the UserID and worked like a charm!
  • xabush
    xabush about 8 years
    @joelmdev, what if the id is customized to be a long instead of string? How does your first approach work in that case?
  • joelmdev
    joelmdev about 8 years
    @avidProgrammer I believe you'd just swap out var claim = new Claim("test", "IdOfYourChoosing"); with new Claim("test", "1234567", ClaimValueTypes.Integer64);. I haven't tested this, but if it doesn't work it should at least put you on the right path.
  • Rajesh Shetty
    Rajesh Shetty over 6 years
    Great and very simple solution :-)
  • Sinaesthetic
    Sinaesthetic about 6 years
    System.NotSupportedException : Invalid setup on a non-virtual (overridable in VB) member: mock => mock.HttpContext