Membership.CreateUser的ASP.NET MVC 3单元测试始终返回MembershipCreateStatus错误

时间:2011-05-31 07:18:05

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

我正在尝试编写单元测试来创建新用户并验证是否发生了所需的重定向。这是我的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);
}

以下是我的测试,使用Moq。无论我设置什么,我总是从一个默认的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"));
}

有人可以告诉我这里发生了什么吗?

2 个答案:

答案 0 :(得分:11)

静态类/方法问题再次发生!

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

您需要做的是明确这种依赖。通过引入一个界面来完成此操作,该界面对成员资格提供者所需的交互进行建模(例如称为IMembershipService)。

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

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

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

答案 1 :(得分:3)

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),因此这些默认设置启动了。