【问题标题】:Mocking User.Identity in ASP.NET MVC在 ASP.NET MVC 中模拟 User.Identity
【发布时间】:2010-06-12 02:42:17
【问题描述】:

我需要为 ASP.NET MVC 2.0 网站创建单元测试。该站点使用 Windows 身份验证。

我一直在阅读有关为处理 HttpContext 的代码模拟 HTTP 上下文的必要性。我觉得我也开始掌握 DI 模式。 (给类一个 IRepository 类型的属性,然后在实例化控制器时传入一个 Repository 对象。)

但是,我不明白的是,Mock 可通过 User.Identity 获得的 Windows Principal 对象的正确方法。这是 HttpContext 的一部分吗?

是否有任何机构提供指向证明这一点的文章的链接(或推荐一本书)?

谢谢,

特雷·卡罗尔

【问题讨论】:

    标签: asp.net asp.net-mvc mocking


    【解决方案1】:

    我已使用 IoC 成功地将其抽象出来。我首先定义了一个类来表示当前登录的用户:

    public class CurrentUser
    {
        public CurrentUser(IIdentity identity)
        {
            IsAuthenticated = identity.IsAuthenticated;
            DisplayName = identity.Name;
    
            var formsIdentity = identity as FormsIdentity;
    
            if (formsIdentity != null)
            {
                UserID = int.Parse(formsIdentity.Ticket.UserData);
            }
        }
    
        public string DisplayName { get; private set; }
        public bool IsAuthenticated { get; private set; }
        public int UserID { get; private set; }
    }
    

    在构造函数中需要一个IIdentity 来设置它的值。对于单元测试,您可以添加另一个构造函数来绕过 IIdentity 依赖项。

    然后我使用 Ninject(选择你最喜欢的 IoC 容器,没关系),并为 IIdentity 创建一个绑定:

    Bind<IIdentity>().ToMethod(c => HttpContext.Current.User.Identity);
    

    然后,在我的控制器内部,我在构造函数中声明了依赖项:

    CurrentUser _currentUser;
    
    public HomeController(CurrentUser currentUser)
    {
        _currentUser = currentUser;
    }
    

    IoC 容器看到HomeController 采用CurrentUser 对象,而CurrentUser 构造函数采用IIdentity。它将自动解决依赖关系,瞧!您的控制器可以知道当前登录的用户是谁。 FormsAuthentication 对我来说似乎工作得很好。您或许可以将此示例改编为 Windows 身份验证。

    【讨论】:

    • 对我来说,这种方法比模拟控制器和原理以及 http 上下文等更可取,因为每次我都忘记了我需要模拟哪些部分并且必须通过其他测试来寻找合适的代码复制和粘贴。在我的例子中,控制器采用 ICurrentUserResolver 接口,它有一个 ResolveCurrentUser() 方法,控制器永远不需要担心当前用户是如何确定的。
    • 谢谢@JohnNelson,你救了我的命
    【解决方案2】:

    我不知道 MVC 2.0,但在较新的版本中,您可以模拟 ControllerContext:

    // create mock principal
    var mocks = new MockRepository(MockBehavior.Default);
    Mock<IPrincipal> mockPrincipal = mocks.Create<IPrincipal>();
    mockPrincipal.SetupGet(p => p.Identity.Name).Returns(userName);
    mockPrincipal.Setup(p => p.IsInRole("User")).Returns(true);
    
    // create mock controller context
    var mockContext = new Mock<ControllerContext>();
    mockContext.SetupGet(p => p.HttpContext.User).Returns(mockPrincipal.Object);
    mockContext.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);
    
    // create controller
    var controller = new MvcController() { ControllerContext = mock.Object };
    

    另见How to unit-test an MVC controller action which depends on authentification in c#?

    【讨论】:

      【解决方案3】:

      Scott Hanselman 展示了in his blog 如何使用IPrincipal 和 ModelBinder 通过模拟 IPrincipal 来更轻松地测试控制器。

      【讨论】:

        【解决方案4】:

        在 MVC4 上模拟用户名和 SID 的示例。 应测试以下操作中的用户名和 SID(Windows 身份验证):

        [Authorize]
        public class UserController : Controller
        {
            public ActionResult Index()
            {
                // get Username
                ViewBag.Username = User.Identity.Name;
        
                // get SID
                var lIdentity = HttpContext.User.Identity as WindowsIdentity;
                ViewBag.Sid = lIdentity.User.ToString();
        
                return View();
            }
        }
        

        我使用MoqVisual Studio Test Tools。测试实现如下:

        [TestMethod]
        public void IndexTest()
        {
            // Arrange
            var myController = new UserController();
            var contextMock = new Mock<ControllerContext>();
            var httpContextMock = new Mock<HttpContextBase>();
            var lWindowsIdentity = new WindowsIdentity("Administrator");
        
            httpContextMock.Setup(x => x.User).Returns(new WindowsPrincipal(lWindowsIdentity));
        
            contextMock.Setup(ctx => ctx.HttpContext).Returns(httpContextMock.Object);
            myController.ControllerContext = contextMock.Object;
        
            // Act
            var lResult = myController.Index() as ViewResult;
        
            // Assert
            Assert.IsTrue(lResult.ViewBag.Username == "Administrator");
            Assert.IsTrue(lResult.ViewBag.Sid == "Any SID Pattern");
        }
        

        【讨论】:

          【解决方案5】:

          我已更改开发环境 global.asax 和 Web.Config 以使用 FormsAuth 强制特定用户。用户名使用相同的 WindowsAuth 格式。见:

          public override void Init()
              {
                  base.Init();
          
                  this.PostAuthenticateRequest += 
                       new EventHandler(MvcApplication_PostAuthenticateRequest);
              }
          
              void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e)
              {
                  FormsAuthentication.SetAuthCookie("Domain\\login", true);
              }
          

          Windows 或 Forms Auth 共享相同的登录模式

          该应用程序将同时使用 Windows 身份验证和表单身份验证。

          【讨论】:

            【解决方案6】:

            要模拟WindowsIdentity,您可以执行以下操作:

            var mockedPrincipal = new Mock<WindowsPrincipal>(WindowsIdentity.GetCurrent());
            
            mockedPrincipal.SetupGet(x => x.Identity.IsAuthenticated).Returns(true);
            mockedPrincipal.SetupGet(x => x.Identity.Name).Returns("Domain\\User1");
            mockedPrincipal.Setup(x => x.IsInRole("Domain\\Group1")).Returns(true);
            mockedPrincipal.Setup(x => x.IsInRole("Domain\\Group2")).Returns(false);
            

            然后使用mockedPrincipal.Object 得到实际的WindowsIdentity

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-02-08
              • 2019-12-13
              • 2010-10-22
              • 1970-01-01
              相关资源
              最近更新 更多