MVC验证的单元测试

Mat*_*ves 75 validation tdd asp.net-mvc unit-testing asp.net-mvc-2

当我在MVC 2预览1中使用DataAnnotation验证时,如何在验证实体时测试我的控制器操作是否在ModelState中放置了正确的错误?

一些代码来说明.一,行动:

    [HttpPost]
    public ActionResult Index(BlogPost b)
    {
        if(ModelState.IsValid)
        {
            _blogService.Insert(b);
            return(View("Success", b));
        }
        return View(b);
    }
Run Code Online (Sandbox Code Playgroud)

这是一个失败的单元测试,我认为应该通过但不是(使用MbUnit和Moq):

[Test]
public void When_processing_invalid_post_HomeControllerModelState_should_have_at_least_one_error()
{
    // arrange
    var mockRepository = new Mock<IBlogPostSVC>();
    var homeController = new HomeController(mockRepository.Object);

    // act
    var p = new BlogPost { Title = "test" };            // date and content should be required
    homeController.Index(p);

    // assert
    Assert.IsTrue(!homeController.ModelState.IsValid);
}
Run Code Online (Sandbox Code Playgroud)

我想除了这个问题,应该我来测试验证,并应在我这种方式测试它?

小智 192

讨厌一个老帖子,但我想我会添加自己的想法(因为我刚遇到这个问题并且在寻找答案时遇到了这个帖子).

  1. 不要在控制器测试中测试验证.您是信任MVC的验证还是自己编写(即不测试其他代码,测试代码)
  2. 如果您确实希望测试验证正在按照您的预期进行,请在模型测试中进行测试(我这样做是为了进行一些更复杂的正则表达式验证).

你真正想要测试的是你的控制器在验证失败时按照你的期望做.这是你的代码,也是你的期望.一旦你意识到你想要测试的全部内容,就很容易进行测试:

[test]
public void TestInvalidPostBehavior()
{
    // arrange
    var mockRepository = new Mock<IBlogPostSVC>();
    var homeController = new HomeController(mockRepository.Object);
    var p = new BlogPost();

    homeController.ViewData.ModelState.AddModelError("Key", "ErrorMessage"); // Values of these two strings don't matter.  
    // What I'm doing is setting up the situation: my controller is receiving an invalid model.

    // act
    var result = (ViewResult) homeController.Index(p);

    // assert
    result.ForView("Index")
    Assert.That(result.ViewData.Model, Is.EqualTo(p));
}
Run Code Online (Sandbox Code Playgroud)

  • 我同意,这应该是正确的答案。正如 ARM 所说:不应测试内置验证。相反,你的控制器的行为应该是被测试的东西。这是最有意义的。 (2认同)
  • 您应该怎么做才能测试自定义验证属性?如果正在使用这些,则不能“信任MVC的验证”。您将如何测试(大概在模型测试中)自定义验证是否有效? (2认同)
  • 我不同意.我们仍然需要验证给定模型是否会产生在此测试中用作前提条件的模型错误.然而,示例代码是1中您自己定义的问题的完美答案.但是,它不是最初问题的答案 (2认同)

Gil*_*ith 87

我遇到了同样的问题,在阅读了Pauls的回答和评论后,我找到了一种手动验证视图模型的方法.

我发现本教程解释了如何手动验证使用DataAnnotations的ViewModel.他们的密钥代码片段即将发布.

我稍微修改了代码 - 在教程中省略了TryValidateObject的第4个参数(validateAllProperties).为了将所有注释都设置为Validate,应将其设置为true.

另外,我将代码重构为通用方法,使ViewModel验证的测试变得简单:

    public static void ValidateViewModel<TViewModel, TController>(this TController controller, TViewModel viewModelToValidate) 
        where TController : ApiController
    {
        var validationContext = new ValidationContext(viewModelToValidate, null, null);
        var validationResults = new List<ValidationResult>();
        Validator.TryValidateObject(viewModelToValidate, validationContext, validationResults, true);
        foreach (var validationResult in validationResults)
        {
            controller.ModelState.AddModelError(validationResult.MemberNames.FirstOrDefault() ?? string.Empty, validationResult.ErrorMessage);
        }
    }
Run Code Online (Sandbox Code Playgroud)

到目前为止,这对我们来说非常有效.

  • 为什么需要使用泛型?如果定义为以下内容,则可以更容易地使用它:void ValidateViewModel(object viewModelToValidate,Controller controller)或甚至更好的扩展方法:public static void ValidateViewModel(此Controller控制器,对象viewModelToValidate) (6认同)

Pau*_*der 7

在测试中调用homeController.Index方法时,您没有使用任何触发验证的MVC框架,因此ModelState.IsValid将始终为true.在我们的代码中,我们直接在控制器中调用辅助程序Validate方法,而不是使用环境验证.我没有太多使用DataAnnotations的经验(我们使用NHibernate.Validators)也许其他人可以提供如何从控制器中调用Validate的指导.

  • 但问题是你基本上是在测试MVC框架 - 而不是你的控制器.您正在尝试确认MVC正在按预期验证您的模型.任何确定性的唯一方法是模拟整个MVC管道并模拟Web请求.这可能比你真正需要知道的还要多.如果您只是测试模型上的数据验证是否设置正确,您可以在没有控制器的情况下执行此操作,只需手动运行数据验证. (3认同)

Mau*_*ice -4

BlogPost您也可以将 actions 参数声明为 ,而不是传入 a FormCollection。然后您可以自己创建BlogPost并调用UpdateModel(model, formCollection.ToValueProvider());.

这将触发对FormCollection.

    [HttpPost]
    public ActionResult Index(FormCollection form)
    {
        var b = new BlogPost();
        TryUpdateModel(model, form.ToValueProvider());

        if (ModelState.IsValid)
        {
            _blogService.Insert(b);
            return (View("Success", b));
        }
        return View(b);
    }
Run Code Online (Sandbox Code Playgroud)

只需确保您的测试为视图表单中要留空的每个字段添加一个空值即可。

我发现这样做,以几行额外的代码为代价,使我的单元测试更类似于在运行时调用代码的方式,从而使它们更有价值。您还可以测试当有人在绑定到 int 属性的控件中输入“abc”时会发生什么。

  • 恕我直言,ARM 的方法更好:) (5认同)
  • 这违背了 MVC 的目的。 (4认同)
  • 我喜欢这种方法,但它似乎是一种倒退,或者至少是我必须在处理 POST 的每个操作中添加一个额外的步骤。 (2认同)
  • 我同意。但是让我的单元测试和实际应用程序以相同的方式工作是值得的。 (2认同)
  • 我同意ARM的答案更好。与传递强类型 Model/ViewModel 对象相比,将 FormCollection 传递给控制器​​操作是不可取的。 (2认同)