考虑.NET程序集中的方法:
public static string GetSecurityContextUserName()
{
//extract the username from request
string sUser = HttpContext.Current.User.Identity.Name;
//everything after the domain
sUser = sUser.Substring(sUser.IndexOf("\\") + 1).ToLower();
return sUser;
}
我想使用Moq框架从单元测试中调用此方法。该程序集是webforms解决方案的一部分。单元测试看起来像这样,但我错过了Moq代码。
//arrange
string ADAccount = "BUGSBUNNY";
string fullADName = "LOONEYTUNES\BUGSBUNNY";
//act
//need to mock up the HttpContext here somehow -- using Moq.
string foundUserName = MyIdentityBL.GetSecurityContextUserName();
//assert
Assert.AreEqual(foundUserName, ADAccount, true, "Should have been the same User Identity.");
问题:
MyIdentityBL.GetSecurityContextUserName()
的静态方法? 答案 0 :(得分:42)
出于这个原因,Webforms众所周知是不可测试的 - 很多代码都可以依赖asp.net管道中的静态类。
为了使用Moq进行测试,您需要重构GetSecurityContextUserName()
方法以使用HttpContextBase
对象的依赖注入。
HttpContextWrapper
位于System.Web.Abstractions
,随附.Net 3.5。它是HttpContext
类的包装器,并扩展为HttpContextBase
,您可以像这样构建HttpContextWrapper
:
var wrapper = new HttpContextWrapper(HttpContext.Current);
更好的是,您可以使用Moq模拟HttpContextBase并设置它的期望。包括登录用户等
var mockContext = new Mock<HttpContextBase>();
有了这个,你可以调用GetSecurityContextUserName(mockContext.Object)
,你的应用程序与静态WebForms HttpContext的耦合要少得多。如果你要做很多依赖于模拟上下文的测试,我强烈建议taking a look at Scott Hanselman's MvcMockHelpers class,它有一个用于Moq的版本。它可以方便地处理许多必要的设置。尽管有这个名字,但你不需要使用MVC - 当我可以重构它们以使用HttpContextBase
时,我成功地使用它与webforms应用程序。
答案 1 :(得分:3)
一般来说,对于ASP.NET单元测试,不应该访问HttpContext.Current,而应该有一个HttpContextBase类型的属性,其值由依赖注入设置(例如在Womp提供的答案中)。
但是,为了测试安全相关的函数,我建议使用Thread.CurrentThread.Principal(而不是HttpContext.Current.User)。使用Thread.CurrentThread的优点是也可以在Web上下文之外重用(并且在Web上下文中的工作方式相同,因为ASP.NET框架始终将两个值设置为相同)。
然后测试Thread.CurrentThread.Principal我通常使用一个范围类,将Thread.CurrentThread设置为测试值,然后重置dispose:
using (new UserResetScope("LOONEYTUNES\BUGSBUNNY")) {
// Put test here -- CurrentThread.Principal is reset when PrincipalScope is disposed
}
这非常适合标准的.NET安全组件 - 其中组件具有已知的接口(IPrincipal)和位置(Thread.CurrentThread.Principal) - 并且可以使用正确使用/检查Thread的任何代码。 CurrentThread.Principal。
基本范围类将类似于以下内容(根据需要进行调整,例如添加角色):
class UserResetScope : IDisposable {
private IPrincipal originalUser;
public UserResetScope(string newUserName) {
originalUser = Thread.CurrentPrincipal;
var newUser = new GenericPrincipal(new GenericIdentity(newUserName), new string[0]);
Thread.CurrentPrincipal = newUser;
}
public IPrincipal OriginalUser { get { return this.originalUser; } }
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (disposing) {
Thread.CurrentPrincipal = originalUser;
}
}
}
另一种方法是,编写您的应用程序以使用注入的安全性详细信息,而不是使用标准安全组件位置。使用GetCurrentUser()方法或类似方法添加ISecurityContext属性,然后在整个应用程序中一致地使用它 - 但如果您要在Web应用程序的上下文中执行此操作,那么您也可以使用预先构建的注入上下文,HttpContextBase。
答案 2 :(得分:1)
使用httpSimulator类,您将能够将HttpContext传递给处理程序
HttpSimulator sim = new HttpSimulator("/", @"C:\intepub\?")
.SimulateRequest(new Uri("http://localhost:54331/FileHandler.ashx?
ticket=" + myticket + "&fileName=" + path));
FileHandler fh = new FileHandler();
fh.ProcessRequest(HttpContext.Current);
HttpSimulator实现了获取HttpContext实例所需的内容。所以你不需要在这里使用Moq。
答案 3 :(得分:1)
[TestInitialize]
public void TestInit()
{
HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
}
你也可以像下面那样进行搜索
var controllerContext = new Mock<ControllerContext>();
controllerContext.SetupGet(p => p.HttpContext.Session["User"]).Returns(TestGetUser);
controllerContext.SetupGet(p => p.HttpContext.Request.Url).Returns(new Uri("http://web1.ml.loc"));
答案 4 :(得分:0)
如果你正在使用CLR安全模型(就像我们一样)那么你需要使用一些抽象函数来获取和设置当前主体,如果你想允许测试,并在获取或设置主体时使用它们。这样做可以让你在相关的任何地方获取/设置主体(通常在Web上的HttpContext
上,以及在其他地方的当前线程上,比如单元测试)。这看起来像是:
public static IPrincipal GetCurrentPrincipal()
{
return HttpContext.Current != null ?
HttpContext.Current.User :
Thread.CurrentThread.Principal;
}
public static void SetCurrentPrincipal(IPrincipal principal)
{
if (HttpContext.Current != null) HttpContext.Current.User = principal'
Thread.CurrentThread.Principal = principal;
}
如果您使用自定义主体,那么这些可以很好地集成到其界面中,例如,Current
下方会调用GetCurrentPrincipal
而SetAsCurrent
会调用SetCurrentPrincipal
。< / p>
public class MyCustomPrincipal : IPrincipal
{
public MyCustomPrincipal Current { get; }
public bool HasCurrent { get; }
public void SetAsCurrent();
}
答案 5 :(得分:0)
这与使用Moq进行单元测试所需的功能并不相关。
通常我们在工作中都有一个分层架构,其中表示层上的代码实际上只是用于安排在UI上显示的内容。这种代码不包含在单元测试中。所有其余的逻辑都驻留在业务层上,业务层不必依赖于表示层(即特定于UI的引用,如HttpContext),因为UI也可能是WinForms应用程序,不一定是Web应用程序
通过这种方式,你可以避免乱搞Mock框架,试图模拟HttpRequests等......尽管通常它仍然是必要的。
答案 6 :(得分:0)
在 ASP.NET MVC Core 中,我使用以下代码来测试依赖于 HttpContext
的控制器:
var controller = new HomeController();
controller.ControllerContext.HttpContext = new DefaultHttpContext();
这是一个示例单元测试:
[Test]
public void Test_HomeController_Index()
{
// Arrange
var controller = new HomeController();
controller.ControllerContext.HttpContext = new DefaultHttpContext();
// Act
var result = controller.Index();
// Assert
var viewResult = result as ViewResult;
Assert.IsNotNull(viewResult);
}