2009-07-31 48 views
35

考虑一个方法在.NET组件:起定量:单元测试依靠HttpContext的方法

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."); 

问题

  • 我如何使用最小起订量来安排与像 'MYDOMAIN \ MYUSER' 一些价值假的HttpContext对象?
  • 我如何在MyIdentityBL.GetSecurityContextUserName()有我的电话,假冒联想到我的静态方法?
  • 你有关于如何提高这个代码/架构有什么建议?

回答

39

因为这个确切的原因,Webforms是臭名昭着的无法测试 - 很多代码可以依赖于asp.net管道中的静态类。

为了使用Moq进行测试,您需要重构GetSecurityContextUserName()方法以使用依赖注入与HttpContextBase对象。

HttpContextWrapper驻留在System.Web.Abstractions,其中附带的.Net 3.5。它为HttpContext类的包装,并延伸HttpContextBase,你可以构造一个HttpContextWrapper就像这样:

var wrapper = new HttpContextWrapper(HttpContext.Current); 

更妙的是,你可以模拟一个HttpContextBase和设置您的期望它使用起订量上。包括登录的用户,等

var mockContext = new Mock<HttpContextBase>(); 

有了这个地方,你可以打电话GetSecurityContextUserName(mockContext.Object),和你的应用程序少得多耦合到静态的WebForms的HttpContext。如果你打算做了很多的依靠嘲笑方面的测试,我强烈建议taking a look at Scott Hanselman's MvcMockHelpers class,其具有与起订量使用的版本。它可以方便地处理很多必要的设置。尽管名字,你不需要用MVC那样做 - 我成功地用它来与web表单的应用程序时,我可以重构他们使用HttpContextBase

+0

不能使用新HttpContextBase(),因为它是一个抽象类(顾名思义)。您需要使用新的HttpContextWrapper()。 – 2009-11-19 17:53:23

+0

谢谢......当我回答这个问题时,我的脑袋超出了我的预期;) – womp 2009-11-19 20:12:09

0

如果你正在使用CLR安全模型(如我们一样的),那么你就需要使用一些抽象的函数来得到,如果你想允许测试设置当前主体,并利用这些获取或设置每当主要。这样做可以让您在任何相关的地方获取/设置主体(通常在网页上的HttpContext以及其他单元测试中的当前线程上)。如果您使用自定义主那么这些可以很好地公平地集成到它的接口,例如下面Current会打电话GetCurrentPrincipalSetAsCurrent会打电话SetCurrentPrincipal

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

:这看起来是这样。

public class MyCustomPrincipal : IPrincipal 
{ 
    public MyCustomPrincipal Current { get; } 
    public bool HasCurrent { get; } 
    public void SetAsCurrent(); 
} 
0

这与使用Moq进行单元测试并非真正相关。

一般来说,我们在工作中都有一个分层架构,其中表示层上的代码实际上只是用来安排在UI上显示的东西。这种代码不包含单元测试。所有其余的逻辑驻留在业务层上,因为UI可能也是一个WinForms应用程序,并且不一定是Web应用程序,所以它不必对表示层具有任何依赖关系(即UI特定引用,如HttpContext) 。

通过这种方式,您可以避免混淆模拟框架,试图模拟HttpRequests等......尽管通常它可能仍然是必需的。

3

一般来说,对于ASP.NET单元测试,而不是访问HttpContext.Current,你应该有一个HttpContextBase类型的属性,它的值是通过依赖注入设置的(例如在Womp提供的答案中)。

但是,对于测试安全相关的功能,我建议使用Thread.CurrentThread.Principal(而不是HttpContext.Current.User)。使用Thread.CurrentThread的优点是可以在Web上下文之外重用(因为ASP.NET框架始终将两个值设置为相同,所以在Web上下文中工作相同)。

为了再测试Thread.CurrentThread.Principal我通常使用该设置Thread.CurrentThread到一个测试值,然后复位处置上一个范围类:

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。

1

看一看这个 http://haacked.com/archive/2007/06/19/unit-tests-web-code-without-a-web-server-using-httpsimulator.aspx

使用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。

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"));