【问题标题】:How do I mock User.Identity.GetUserId()?如何模拟 User.Identity.GetUserId()?
【发布时间】:2014-05-10 20:05:32
【问题描述】:

我正在尝试对包含以下行的代码进行单元测试:

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

我只是卡住了一点,因为我无法理解:

User.Identity.GetUserId()

返回一个值。我在控制器的设置中一直在尝试以下操作:

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

但它给出了“NotSupportedException 未被用户代码处理”的错误。我还尝试了以下方法:

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;

基于我在 stackoverflow 上找到的一些代码,但这会返回相同的错误“NotSupportedException 未被用户代码处理”。

任何关于我如何进行的帮助将不胜感激。谢谢。

【问题讨论】:

  • 实现这一点的标准方法是创建一个新服务,即IIdentity,它将您的代码从GetUserId 扩展方法中抽象出来,并将其设置为对调用您@ 的代码的依赖项987654327@
  • 刚刚意识到我原来的答案只适用于派生的 ApiControllers。使用派生控制器的选项更新了我的答案,以及适用于两者的第二种方法。

标签: c# .net unit-testing moq asp.net-mvc-5


【解决方案1】:

您无法设置 GetUserId 方法,因为它是一种扩展方法。相反,您必须在用户的 IIdentity 属性上设置名称。 GetUserId 将使用它来确定 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");

更多信息请参见:http://msdn.microsoft.com/en-us/library/microsoft.aspnet.identity.identityextensions.getuserid(v=vs.111).aspx

【讨论】:

  • 原来我实际上只是在做其他愚蠢的事情,而这根本不是我的问题。但你的答案是正确的。谢谢。
【解决方案2】:

您不能直接模拟扩展方法,因此最好的办法是向下钻取,直到找到可模拟的扩展方法所依赖的属性和方法。

在这种情况下,IIdentity.GetuUserId() 是一种扩展方法。我会发布它,但该库目前不是开源的,所以你必须自己看看GetUserId() 依赖于ClaimsIdentity.FindFirstValue()。事实证明,这也是一种扩展方法,但它依赖于ClaimsIdentity.FindFirst(),它被标记为virtual。现在我们有了接缝,所以我们可以执行以下操作:

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"

更新:我刚刚意识到我在上面发布的解决方案仅适用于派生的 ApiControllers(如 Web API)。 MVC Controller 类没有可设置的 User 属性。好在很容易通过ControllerControllerContext进去就可以达到想要的效果:

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
};

但是,如果您要使用 User 对象来获取用户的 ID,那么还有另一种方法适用于任一类型的控制器,并且需要的代码更少:

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

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

现在,当您需要用户 ID 时,无需调用 User.Identity.GetUserId(),只需调用 GetUserId()。当您需要在测试中模拟出用户 ID 时,您只需这样做:

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

这两种方法各有利弊。第一种方法更彻底、更灵活,并使用了两种控制器类型上已经存在的接缝,但它的代码也多得多,而且读起来也不太好。第二种方法更简洁,IMO 更具表现力,但它需要额外的代码来维护,如果你不使用 Func 调用来获取用户的 Id,你的测试将无法工作。选择最适合您的情况的一种。

【讨论】:

  • +1 配置对ClaimsIdentity.FindFirst() 的调用解决了我的问题。下面是一个使用 FakeItEasy 语法的例子:var fakeClaim = A.Fake&lt;Claim&gt;(x =&gt; x.WithArgumentsForConstructor(() =&gt; new Claim(ClaimTypes.NameIdentifier, FakeEmpIDString)));var fakeIdentity = A.Fake&lt;ClaimsIdentity&gt;();A.CallTo(() =&gt; fakeIdentity.FindFirst(ClaimTypes.NameIdentifier)).Returns(fakeClaim); 我的设置看起来有点不同;无法直接设置 Controller.User 属性,因此我注入 ControllerContext 并将其 HttpContext.User 属性设置为 ClaimsPrinciple 对象。
  • 哎呀。第二个工作完美。这样做是为了获取 UserID 并像魅力一样工作!
  • @joelmdev,如果将 id 自定义为长而不是字符串怎么办?在这种情况下,您的第一种方法如何工作?
  • @avidProgrammer 我相信你会把var claim = new Claim("test", "IdOfYourChoosing"); 换成new Claim("test", "1234567", ClaimValueTypes.Integer64);。我没有对此进行测试,但如果它不起作用,它至少应该让你走上正确的道路。
  • 伟大且非常简单的解决方案 :-)
【解决方案3】:

这对我有用

使用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); 

现在您可以从那些 IIdentity 扩展方法中获取值:

例如。

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

使用 ILSpy 查看 GetUserId() 方法时会显示

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;
}

所以 GetUserId 需要声明“nameidentifier

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多