如何模拟 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();
其中NameWithoutDomain
是IIdentity
的扩展方法。
每当我 运行 它时,我总是得到 "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
。测试愉快! :-)
在我的 MVC 项目中,我正在为 return 视图的控制器构建单元测试。
这个controller的action结果构建了一个viewmodel,这个viewmodel的constructor调用
HttpContext.Current.User.Identity.NameWithoutDomain().ToUpper();
其中NameWithoutDomain
是IIdentity
的扩展方法。
每当我 运行 它时,我总是得到 "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
。测试愉快! :-)