ASP.NET MVC单元测试时如何对含有ModelState.IsValid的Action进行测试

  下面的例子来至Asp.Net MVC 2的项目模板。

  首先是一个实体类:

实体类
      
      
    
    
[PropertiesMustMatch( " Password " , " ConfirmPassword " , ErrorMessage = " The password and confirmation password do not match. " )]
public class RegisterModel
{
[Required]
[DisplayName(
" User name " )]
public string UserName { get ; set ; }

[Required]
[DataType(DataType.EmailAddress)]
[DisplayName(
" Email address " )]
public string Email { get ; set ; }

[Required]
[ValidatePasswordLength]
[DataType(DataType.Password)]
[DisplayName(
" Password " )]
public string Password { get ; set ; }

[Required]
[DataType(DataType.Password)]
[DisplayName(
" Confirm password " )]
public string ConfirmPassword { get ; set ; }
}

 

  然后是Action:

代码
      
      
    
    
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
MembershipCreateStatus createStatus = MembershipService.CreateUser(model.UserName, model.Password, model.Email);

if (createStatus == MembershipCreateStatus.Success)
{
FormsService.SignIn(model.UserName,
false /* createPersistentCookie */ );
return RedirectToAction( " Index " , " Home " );
}
else
{
ModelState.AddModelError(
"" , AccountValidation.ErrorCodeToString(createStatus));
}
}

// If we got this far, something failed, redisplay form
ViewData[ " PasswordLength " ] = MembershipService.MinPasswordLength;
return View(model);
}

 

  如果你对这个Action写单元测试,你会发现没办法测试输入不完整的情况,比如下面的代码:

单元测试
      
      
    
    
[TestMethod()]
public void RegisterTest()
{
AccountController target
= new AccountController()
RegisterModel model
= new RegisterModel();
ActionResult actual;
actual
= target.Register(model);
var view
= actual as ViewResult;
Assert.IsNotNull(view);
Assert.IsNotNull(view.ViewData.ModelState[
" PasswordLength " ]);
}

 

  这个单元测试不会跑完,因为注册的时候由于用户名是null,会抛出异常。因为这个判断:

     
     
   
   
if (ModelState.IsValid)

 

没有起作用,原因是Asp.Net MVC框架会在调用这个Action之前进行模型验证,由于单元测试直接测试这个Action,并没有进行模型验证的步骤,所以ModelState.IsValid仍然是默认值true,这个场景下无法验证传入的参数是否符合预期。

      解决办法有2个,第一是编写单元测试的人知道模型是否正确,只要改变ModelState.ISValid的值即可。如下代码:

方法一
      
      
    
    
///
/// Register 的测试
///

[TestMethod()]
public void RegisterTest()
{
AccountController target
= new AccountController()
RegisterModel model
= new RegisterModel();
ActionResult actual;
target.ViewData.ModelState.AddModelError(
"" , " 模型没有正确赋值 " );
actual
= target.Register(model);
var view
= actual as ViewResult;
Assert.IsNotNull(view);
Assert.IsNotNull(view.ViewData.ModelState[
" PasswordLength " ]);
}

 

通过增加一个错误信息,ModelState.IsValid值就会被置位false,这样就可以测试Action中模型不正确的流程。这种解决办法的思路是模型验证已经由微软Asp.Net MVC团队测试过了,无需再测试。如果你需要测试你配置的Attribute是否正确,可以在另外的地方测试,这和Action无关。

  另外一种思路就是主动验证模型,并把验证结果添加到ModelState集合中去,这也就是Asp.Net MVC框架内部所作的工作。如果使用.Net 4.0,在System.ComponentModel.DataAnnotations命名空间中新增了ValidationContext类,可以在任何需要的地方对模型进行验证。这样只需要简单的写个帮助类,在单元测试中手动调用这个方法即可验证模型:

模型验证帮助类
      
      
    
    
public static void AddValidationErrors( this ModelStateDictionary modelState, object model)
{
var context
= new ValidationContext(model, null , null );
var results
= new List < ValidationResult > ();
Validator.TryValidateObject(model, context, results,
true );
foreach (var result in results)
{
var name
= result.MemberNames.First();
modelState.AddModelError(name, result.ErrorMessage);
}
}

 

如果使用.Net 3.5,很遗憾,没有这个类,但是可以使用Asp.Net MVC内部框架的验证机制。Controller类中定义了一个方法:

protected internal bool TryValidateModel(object model); 这个方法对正确的模型,返回true,错误的返回false。这个方法并不是公开的,可以公开一个新的方法调用它。

     
     
   
   
public bool InvokeValidateModel( object model)
{
return TryValidateModel(model);
}

 

最后代码如下:   

最终代码
      
      
    
    
[TestMethod()]
public void RegisterTest()
{
AccountController target
= new AccountController()
RegisterModel model
= new RegisterModel();
ActionResult actual;
target.InvokeValidateModel(model);
actual
= target.Register(model);
var view
= actual as ViewResult;
Assert.IsNotNull(view);
Assert.IsNotNull(view.ViewData.ModelState[
" PasswordLength " ]);
}

 

为了方便,可以把InvokeValidateModel方法定义在一个继承Controller的父类中。

  通过上面两种办法,现在我们可以正确的测试包含了ModelState.IsValid代码的Action方法了。

你可能感兴趣的:(ASP.NET MVC单元测试时如何对含有ModelState.IsValid的Action进行测试)