zoukankan      html  css  js  c++  java
  • 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的值即可。如下代码:

    方法一
    /// <summary>
    ///Register 的测试
    ///</summary>
    [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方法了。

  • 相关阅读:
    C++利用SOAP开发WebService
    C++中使用soap toolkit访问webService详解
    第一次课堂作业之Circle
    第四次作业(计算器第二步)
    第三次作业之Calculator项目随笔
    C++视频课程小结(3)
    C++视频课程小结(2)
    C++视频课程小结(1)
    第二次作业之视频课程题
    第二次作业之编程题
  • 原文地址:https://www.cnblogs.com/wenhx/p/Asp_Net_MVC_Unit_Tests_ModelState_IsValid.html
Copyright © 2011-2022 走看看