代码之家  ›  专栏  ›  技术社区  ›  p.campbell

moq:依赖httpContext的单元测试方法

  •  37
  • p.campbell  · 技术社区  · 15 年前

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

    问题 :

    • 我如何使用moq来安排一个具有“mydomain\myuser”等值的假httpcontext对象?
    • 如何将该伪代码与调用关联到静态方法中 MyIdentityBL.GetSecurityContextUserName() ?
    • 您对如何改进此代码/体系结构有什么建议吗?
    6 回复  |  直到 8 年前
        1
  •  41
  •   womp    15 年前

    众所周知,WebForms不稳定的原因正是如此——许多代码可以依赖ASP.NET管道中的静态类。

    为了用moq测试这一点,您需要重构 GetSecurityContextUserName() 使用依赖项注入的方法 HttpContextBase 对象。

    HttpContextWrapper 驻留在 System.Web.Abstractions ,与.NET 3.5一起提供。它是一个包装 HttpContext 类和扩展 HTTPTCP数据库 ,您可以构造一个 httpContextWrapper 就像这样:

    var wrapper = new HttpContextWrapper(HttpContext.Current);
    

    更好的是,您可以模拟httpContextBase并使用moq设置您对它的期望。包括登录用户等。

    var mockContext = new Mock<HttpContextBase>();
    

    有了这个,你可以打电话 GetSecurityContextUserName(mockContext.Object) 并且您的应用程序与静态WebForms httpContext的耦合要少得多。如果你要做很多依赖于模拟上下文的测试,我强烈建议 taking a look at Scott Hanselman's MvcMockHelpers class ,它有一个用于MOQ的版本。它可以方便地处理许多必要的设置。尽管有这个名字,但你不必使用MVC——我在WebForms应用程序中成功地使用它,当我可以重构它们来使用时 HTTPTCP数据库 .

        2
  •  3
  •   Sly Gryphon    13 年前

    通常,对于ASP.NET单元测试,而不是访问httpcontext.current,您应该有一个httpcontextbase类型的属性,该属性的值由依赖项注入设置(例如在womp提供的答案中)。

    但是,对于测试与安全相关的函数,我建议使用thread.currenthread.principal(而不是httpcontext.current.user)。使用thread.currentThread还具有可在Web上下文外部重用的优点(并且在Web上下文中工作相同,因为ASP.NET框架始终将两个值设置为相同的值)。

    为了测试thread.currenthread.principal,我通常使用一个作用域类,该类将thread.currenthread设置为测试值,然后在释放时重置:

    using (new UserResetScope("LOONEYTUNES\BUGSBUNNY")) {
        // Put test here -- CurrentThread.Principal is reset when PrincipalScope is disposed
    }
    

    这很适合标准的.NET安全组件——其中一个组件有一个已知的接口(iprincipal)和位置(thread.currenthread.principal)——并且可以使用任何正确使用/检查thread.currenthread.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应用程序的上下文中执行此操作,则Y您还可以使用预构建的注入上下文httpContextBase。

        3
  •  1
  •   Nkosi    8 年前

    看看这个 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。

        4
  •  1
  •   Nkosi    8 年前
    [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"));
    
        5
  •  0
  •   Greg Beech    15 年前

    如果您使用的是clr安全模型(和我们一样),那么如果您想允许测试,就需要使用一些抽象的函数来获取和设置当前主体,并在获取或设置主体时使用这些函数。这样做允许您在任何相关的地方获取/设置主体(通常在 HttpContext 在Web上,以及在其他地方的当前线程上,如单元测试)。这看起来像:

    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 .

    public class MyCustomPrincipal : IPrincipal
    {
        public MyCustomPrincipal Current { get; }
        public bool HasCurrent { get; }
        public void SetAsCurrent();
    }
    
        6
  •  0
  •   Juri    15 年前

    在使用moq进行单元测试时,这与您所需要的并不真正相关。

    一般来说,我们在工作中有一个分层的体系结构,其中表示层上的代码实际上只是用来安排在UI上显示的内容。这种代码不包含在单元测试中。其余的逻辑都驻留在业务层上,不必依赖于表示层(即特定于用户界面的引用,如httpContext),因为用户界面也可能是WinForms应用程序,而不一定是Web应用程序。

    通过这种方式,您可以避免与模拟框架混淆,尝试模拟httprequest等……尽管通常它仍然是必要的。