【问题标题】:ASP.NET MVC 3 unit tests for Membership.CreateUser always return MembershipCreateStatus errorMembership.CreateUser 的 ASP.NET MVC 3 单元测试总是返回 MembershipCreateStatus 错误
【发布时间】:2011-05-31 07:18:04
【问题描述】:

我正在尝试编写一个单元测试来创建新用户并验证是否发生了所需的重定向。这是我的Register 操作,它几乎是来自 VS 模板的开箱即用代码:

[HttpPost]
public ActionResult Register(RegisterModel model)
{
    if (ModelState.IsValid)
    {
        // Attempt to register the user
        MembershipCreateStatus createStatus;
        Membership.CreateUser(model.UserName, model.Password, model.Email, null, null, true, null, out createStatus);

        if (createStatus == MembershipCreateStatus.Success)
        {
            FormsAuthentication.SetAuthCookie(model.UserName, false /* createPersistentCookie */);
            return RedirectToAction("Index", "Home");
        }
        else
        {
            ModelState.AddModelError(String.Empty, ErrorCodeToString(createStatus));
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

以下是我的测试,使用起订量。无论我设置什么,我总是会从默认的MembershipCreateStatus 错误消息之一中收到错误消息。例如:

提供的密码无效。请输入有效的密码值

提供的密码找回答案无效

我尝试将CreateUser 方法更改为仅调用用户名、密码和电子邮件过载,但这并不重要。就像在某处执行密码策略的检查一样。

public void RegisterPost_WithAuthenticatedUser_RedirectsToHomeControllerIfSuccessful()
{
    // Arrange
    var accountController = new AccountController();
    var mockContext = GetMockRequestContext();

    ControllerContext controllerContext = new ControllerContext(mockContext.Object, accountController);
    accountController.ControllerContext = controllerContext;

    RegisterModel registerModel = new RegisterModel() { UserName = "someone", Email = "someone@example.com", Password = "user", ConfirmPassword = "password" };

    // Act
    var result = accountController.Register(registerModel);

    // Assert
    Assert.That(result.RouteData.Values["Controller"], Is.EqualTo("Home"));
    Assert.That(result.RouteData.Values["Action"], Is.EqualTo("Index"));
}

谁能告诉我这里发生了什么?

【问题讨论】:

  • 你的GetMockRequestContext()怎么样?
  • @abatishchev 我的朋友指出我正在测试 ASP.NET 中已经测试过的内部组件,所以我正在改变我的测试方法。但它看起来像这样:Mock<RequestContext> GetMockContext(string roleName, bool isInRole, bool isAuthenticated){ var mockContext = new Mock<RequestContext>(); var fakeUser = new Mock<IPrincipal>(); fakeUser.Setup(p => p.IsInRole(roleName)).Returns(isInRole); fakeUser.Setup(p => p.Identity.IsAuthenticated).Returns(isAuthenticated); mockContext.Setup(c => c.HttpContext.User).Returns(fakeUser.Object);}

标签: c# unit-testing asp.net-mvc-3 tdd moq


【解决方案1】:

静态类/方法问题再次出现!

静态方法Membership.CreateUser在这里是一个隐藏的依赖,因为看起来你的控制器没有依赖,但实际上它依赖于这个方法,并且你没有替换(或能够替换)这个依赖您的测试,以便您可以控制交互。

你需要做的,是明确这个依赖。通过引入一个接口来模拟与成员资格提供者所需的交互(比如称为IMembershipService)来做到这一点。

创建一个默认实现,它只委托给现有的静态方法,如Membership.CreateUser()。在您的控制器中,在构造函数中需要此接口的实例(或者如果您必须在默认构造函数中创建默认实现的实例 - 不是首选选项,但是...)

然后在您的测试中创建此接口的模拟并设置所需的预期,并将此模拟传递给您的控制器,并验证它是否符合您的预期。

如果您不使用模拟,那么每次测试运行时,您都会在数据库中不断创建新用户。这可能没问题,如果你每次都重置你的数据库,但从长远来看,使用模拟更简单、更快,尽管设置起来需要更多的努力,因为你必须创建一个无 null arg 构造函数控制器并引入一些接口将隐式依赖分解为显式依赖。

【讨论】:

  • 这也是我的方法,实际上也是我在我正在处理的应用程序上所做的。我认为默认的 asp.net mvc 3 应用程序已经附带了 IMembershipService。
【解决方案2】:

Membership.CreateUser 只是一个静态包装器,任务实际上委托给配置的MembershipProvider 实现。

如果您未配置成员资格提供程序,则使用在 machine.config 中配置的默认提供程序。对于 .NET v4.0,它在我的机器上看起来像这样:

<membership>
   <providers>
    <add name="AspNetSqlMembershipProvider" 
             type="System.Web.Security.SqlMembershipProvider, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" 
             connectionStringName="LocalSqlServer" 
             enablePasswordRetrieval="false" 
             enablePasswordReset="true" 
             requiresQuestionAndAnswer="true" 
             applicationName="/" 
             requiresUniqueEmail="false" 
             passwordFormat="Hashed" 
             maxInvalidPasswordAttempts="5" 
             minRequiredPasswordLength="7" 
             minRequiredNonalphanumericCharacters="1" 
             passwordAttemptWindow="10" 
             passwordStrengthRegularExpression=""/>
   </providers>
</membership>

请注意属性requiresQuestionAndAnswerminRequiredPasswordLength 默认执行某些规则。

我猜您确实在 Web 应用程序中配置了自己的 MembershipProvider,但很可能您忘记了为单元测试接管此配置(进入测试项目的 app.config),因此这些默认设置开始生效。

【讨论】:

  • +1 虽然这可能会起作用(并且是一个解决方案),但我相信更好的解决方案是通过引入接口来打破对静态方法的依赖
  • 虽然这种方法可行,但它不是单元测试的好方法。通过突然添加 AspNetSqlMembershipProvider,您的测试将依赖于 SQL 数据库,这会影响测试的性能和可重复性。正如 Sam 所指出的,请改用抽象。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-08-03
  • 1970-01-01
  • 2020-07-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-16
相关资源
最近更新 更多