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

2019-01-07 18:10发布

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.

3条回答
Melony?
2楼-- · 2019-01-07 18:31

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

查看更多
贪生不怕死
3楼-- · 2019-01-07 18:34

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.

查看更多
来,给爷笑一个
4楼-- · 2019-01-07 18:35

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"

查看更多
登录 后发表回答