C# ASP.NET MVC 3成员资格单元测试。CreateUser始终返回MembershipCreateStatus错误
我正在尝试编写一个单元测试,用于创建新用户并验证是否发生了所需的重定向。下面是我的C# ASP.NET MVC 3成员资格单元测试。CreateUser始终返回MembershipCreateStatus错误,c#,unit-testing,asp.net-mvc-3,tdd,moq,C#,Unit Testing,Asp.net Mvc 3,Tdd,Moq,我正在尝试编写一个单元测试,用于创建新用户并验证是否发生了所需的重定向。下面是我的注册操作,它几乎是VS模板中的现成代码: [HttpPost] public ActionResult Register(RegisterModel model) { if (ModelState.IsValid) { // Attempt to register the user MembershipCreateStatus createStatus;
注册
操作,它几乎是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"));
}
有人能告诉我这里发生了什么吗?成员资格。CreateUser只是一个静态包装器,任务实际上委托给配置的
成员资格提供程序
实现
如果未配置成员资格提供程序,将使用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>
请注意默认情况下强制执行某些规则的属性requiresQuestionAndAnswer
和minRequiredPasswordLength
我猜您确实在web应用程序中配置了自己的MembershipProvider,但很可能您忘记了为单元测试(进入测试项目的app.config)接管此配置,因此这些默认设置开始生效。静态类/方法问题再次出现 静态方法
Membership.CreateUser
在这里是一个隐藏的依赖项,因为它看起来您的控制器没有依赖项,但实际上它依赖于此方法,并且您在测试中没有替换(或能够替换)此依赖项,因此您可以控制交互
您需要做的是使这个依赖关系显式化。通过引入一个接口来实现这一点,该接口模拟了与成员资格提供者的交互(比如称为IMembershipService
)
创建一个默认实现,它只委托给现有的静态方法,如Membership.CreateUser()
。在您的控制器中,您需要在构造函数中创建此接口的实例(或者在默认构造函数中创建默认实现的实例,如果您必须-不是首选选项,而是…)
然后在测试中创建此接口的模拟,并设置所需的期望值,然后将此模拟传递给控制器,并验证它是否符合预期
如果不使用mock,那么每次测试运行时都会在数据库中创建新用户。如果每次都重置db,这可能没问题,但从长远来看,使用模拟会更简单、更快,尽管设置起来需要花费更多的精力,因为您必须创建一个无空arg构造函数控制器,并引入一些接口来将隐式依赖项分解为显式依赖项。您的
GetMockRequestContext()如何设置
look like?@abatishchev我的朋友指出,我正在测试ASP.NET中已经测试过的内部组件,所以我正在改变我的测试方法。但是它看起来是这样的:Mock-GetMockContext(string-roleName,bool-isInRole,bool-isAuthenticated){var-mockContext=new-Mock();var-fakeUser=new-Mock();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);}
+1虽然这可能会起作用(并且是一个解决方案),但我认为更好的解决方案是通过引入接口来打破对静态方法的依赖。虽然这种方法可行,但对于单元测试来说不是一个好方法。通过突然添加AspNetSqlMembershipProvider,您的测试将依赖于SQL数据库,这将分别影响测试的性能和可重复性。正如Sam所指出的,使用一个抽象来代替。这也是我的方法,并且实际上是我在所处理的应用程序上所做的。我认为默认的asp.net mvc 3应用程序已经附带了IMembershipService。