如何模拟 HttpContext 以访问 IIdentity 的扩展方法?

How to Mock HttpContext to access an extension method of IIdentity?

在我的 MVC 项目中,我正在为 return 视图的控制器构建单元测试。

这个controller的action结果构建了一个viewmodel,这个viewmodel的constructor调用

HttpContext.Current.User.Identity.NameWithoutDomain().ToUpper();

其中NameWithoutDomainIIdentity的扩展方法。

每当我 运行 它时,我总是得到 "the object cannot be found" 我很好奇如何适当地模拟它以进行单元测试?

扩展方法:

public static class SystemWebExtension
{
    public static string NameWithoutDomain(this IIdentity identity)
    {
        return identity.Name.Split('\').Last();
    }
}

模型构造函数:

public Model()
{
    this.PreparedByUser = HttpContext.Current.User.Identity.NameWithoutDomain().ToUpper();
    this.AuthorizedBy = true;
    this.AuthorizedByUser = HttpContext.Current.User.Identity.NameWithoutDomain().ToUpper();
    this.IssueCredit = true;
    this.CreateUser = string.IsNullOrEmpty(this.CreateUser) ? Environment.UserName.ToLower() : this.CreateUser;
    this.CreateDate = this.CreateDate.HasValue ? this.CreateDate : DateTime.Now;
    this.UpdateUser = string.IsNullOrEmpty(this.CreateUser) ? null : Environment.UserName.ToLower();
    this.UpdateDate = this.CreateDate.HasValue ? DateTime.Now : (DateTime?) null;
}

避免耦合到 HttpContext,因为它在单元测试时不可用。

您应该明确地将必要的值注入模型

public Model(IIdentity identity) {
    this.PreparedByUser = identity.NameWithoutDomain().ToUpper();
    this.AuthorizedBy = true;
    this.AuthorizedByUser = identity.NameWithoutDomain().ToUpper();
    this.IssueCredit = true;
    this.CreateUser = string.IsNullOrEmpty(this.CreateUser) ? Environment.UserName.ToLower() : this.CreateUser;
    this.CreateDate = this.CreateDate.HasValue ? this.CreateDate : DateTime.Now;
    this.UpdateUser = string.IsNullOrEmpty(this.CreateUser) ? null : Environment.UserName.ToLower();
    this.UpdateDate = this.CreateDate.HasValue ? DateTime.Now : (DateTime?) null;
}

由于使用了控制器,您可以访问其属性中的所需信息

//...

var model = new Model(this.User.Identity);

return View(model);

并且在测试时,您可以通过其控制器上下文配置控制器

//...
var username = "some username here"
var identity = new GenericIdentity(username, "");
identity.AddClaim(new Claim(ClaimTypes.Name, username));
var principal = new GenericPrincipal(identity, roles: new string[] { });
var user = new ClaimsPrincipal(principal);

var context = new Mock<HttpContextBase>();
context.Setup(_ => _.User).Returns(user);

//...

controller.ControllerContext = new ControllerContext(context.Object, new RouteData(),  controller );

您不能模拟扩展方法,您必须模拟扩展方法内部发生的事情,以便它 return 是您想要的值。

为了确保 NameWithoutDomain return 是您想要的,您可以模拟 IIdentity 并使其 return 成为您想要的用户名,如下所示:

var identityMock = new Mock<IIdentity>();
identityMock.SetupGet(x => x.Name).Returns("UsernameWithoutDomain");

如果您想要测试 NameWithoutDomain 是否执行了预期的操作,即拆分字符串,那么您可以编写一个单独的测试来调用此扩展方法。

至于 HttpContext,您可以在创建控制器之前实例化它,然后将其传递给模拟的 IIdentity,例如像这样(使用 WebAPI2 测试):

var identityMock = new Mock<IIdentity>();
identityMock.SetupGet(x => x.Name).Returns("UsernameWithoutDomain");

HttpContext.Current =
    new HttpContext(new HttpRequest(null, "http://test.com", null), new HttpResponse(null))
    {
        User = new GenericPrincipal(identityMock.Object, new[] {"Role1"})
    };

var myController = new AccountController();

然后当您创建用于测试的控制器实例时,HttpContext.Current.User.Identity.Name 将 return UsernameWithoutDomain。测试愉快! :-)