MVC 验证的单元测试
当我在 MVC 2 Preview 1 中使用 DataAnnotation 验证时,如何测试我的控制器操作在验证实体时是否在 ModelState 中放入了正确的错误?
一些代码来说明。 首先,行动:
[HttpPost]
public ActionResult Index(BlogPost b)
{
if(ModelState.IsValid)
{
_blogService.Insert(b);
return(View("Success", b));
}
return View(b);
}
这是一个失败的单元测试,我认为应该通过但没有(使用 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);
}
我想除了这个问题之外,我应该测试验证,我应该以这种方式测试它吗?
How can I test that my controller action is putting the correct errors in the ModelState when validating an entity, when I'm using DataAnnotation validation in MVC 2 Preview 1?
Some code to illustrate. First, the action:
[HttpPost]
public ActionResult Index(BlogPost b)
{
if(ModelState.IsValid)
{
_blogService.Insert(b);
return(View("Success", b));
}
return View(b);
}
And here's a failing unit test that I think should be passing but isn't (using 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);
}
I guess in addition to this question, should I be testing validation, and should I be testing it in this way?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
我也遇到了同样的问题,在阅读了 Paul 的回答和评论后,我寻找了一种手动验证视图模型的方法。
我发现这个教程,解释了如何手动验证使用 DataAnnotations 的 ViewModel。 他们的关键代码片段位于帖子的末尾。
我稍微修改了代码 - 在教程中省略了 TryValidateObject 的第四个参数 (validateAllProperties)。 为了让所有注释都进行验证,应将其设置为 true。
另外,我将代码重构为通用方法,以使 ViewModel 验证测试变得简单:
到目前为止,这对我们来说效果非常好。
I had been having the same problem, and after reading Pauls answer and comment, I looked for a way of manually validating the view model.
I found this tutorial which explains how to manually validate a ViewModel that uses DataAnnotations. They Key code snippet is towards the end of the post.
I amended the code slightly - in the tutorial the 4th parameter of the TryValidateObject is omitted (validateAllProperties). In order to get all the annotations to Validate, this should be set to true.
Additionaly I refactored the code into a generic method, to make testing of ViewModel validation simple:
So far this has worked really well for us.
讨厌破坏旧帖子,但我想我应该添加自己的想法(因为我刚刚遇到这个问题并在寻求答案时遇到了这篇文章)。
您真正想要在这里测试的是您的控制器在验证失败时是否执行您期望的操作。 这就是您的代码和您的期望。 一旦您意识到这就是您想要测试的全部内容,测试就很容易:
Hate to necro a old post, but I thought I'd add my own thoughts (since I just had this problem and ran across this post while seeking the answer).
What you really want to test here is that your controller does what you expect it to do when validation fails. That's your code, and your expectations. Testing it is easy once you realize that's all you want to test:
我今天正在研究这个,我发现 这篇博文由 Roberto Hernández (MVP) 撰写,它似乎提供了在单元测试期间为控制器操作触发验证器的最佳解决方案。 这将在验证实体时将正确的错误放入 ModelState 中。
I was researching this today and I found this blog post by Roberto Hernández (MVP) that seems to provide the best solution to fire the validators for a controller action during unit testing. This will put the correct errors in the ModelState when validating an entity.
这并不能完全回答您的问题,因为它放弃了 DataAnnotations,但我会添加它,因为它可能会帮助其他人为其控制器编写测试:
您可以选择不使用 System.ComponentModel.DataAnnotations 提供的验证,但仍然可以使用 ViewData.ModelState 对象,通过使用其
AddModelError
方法和一些其他验证机制。 例如:这仍然可以让您利用 MVC 生成的
Html.ValidationMessageFor()
内容,而无需使用DataAnnotations
。 您必须确保与AddModelError
一起使用的密钥与视图期望的验证消息匹配。然后控制器变得可测试,因为验证是显式进行的,而不是由 MVC 框架自动完成。
This doesn't exactly answer your question, because it abandons DataAnnotations, but I'll add it because it might help other people write tests for their Controllers:
You have the option of not using the validation provided by System.ComponentModel.DataAnnotations but still using the ViewData.ModelState object, by using its
AddModelError
method and some other validation mechanism. E.g:This still lets you take advantage of the
Html.ValidationMessageFor()
stuff that MVC generates, without using theDataAnnotations
. You have to make sure the key you use withAddModelError
matches what the view is expecting for validation messages.The controller then becomes testable because the validation is happening explicitly, rather than being done automagically by the MVC framework.
我同意 ARM 有最好的答案:测试控制器的行为,而不是内置验证。
但是,您还可以对模型/视图模型是否定义了正确的验证属性进行单元测试。 假设您的 ViewModel 如下所示:
此单元测试将测试
[Required]
属性是否存在:I agree that ARM has the best answer: test the behaviour of your controller, not the built-in validation.
However, you can also unit test that your Model/ViewModel has the correct validation attributes defined. Let's say your ViewModel looks like this:
This unit test will test for the existence of the
[Required]
attribute:与ARM相比,我没有挖坟的问题。 这是我的建议。 它建立在 Giles Smith 的答案之上,适用于 ASP.NET MVC4(我知道问题是关于 MVC 2 的,但 Google 在寻找答案时不会歧视,而且我无法在 MVC2 上进行测试。)
我没有将验证代码放在通用静态方法中,而是将其放在测试控制器中。 控制器拥有验证所需的一切。 因此,测试控制器如下所示:
当然,该类不需要是受保护的内部类,这是我现在使用它的方式,但我可能会重用该类。 如果某处有一个模型 MyModel 用漂亮的数据注释属性装饰,那么测试看起来像这样:
此设置的优点是我可以重用测试控制器来测试所有模型,并且可以扩展它模拟更多有关控制器的信息或使用控制器具有的受保护方法。
希望能帮助到你。
In contrast to ARM, I don't have a problem with grave digging. So here is my suggestion. It builds on the answer of Giles Smith and works for ASP.NET MVC4 (I know the question is about MVC 2, but Google doesn't discriminate when looking for answers and I cannot test on MVC2.)
Instead of putting the validation code in a generic static method, I put it in a test controller. The controller has everything needed for validation. So, the test controller looks like this:
Of course the class does not need to be a protected innerclass, that is the way I use it now but I probably am going to reuse that class. If somewhere there is a model MyModel that is decorated with nice data annotation attributes, then the test looks something like this:
The advantage of this setup is that I can reuse the test controller for tests of all my models and may be able to extend it to mock a bit more about the controller or use the protected methods that a controller has.
Hope it helps.
如果您关心验证但不关心它是如何实现的,如果您只关心最高抽象级别的操作方法的验证,无论它是使用 DataAnnotations、ModelBinders 甚至 ActionFilterAttributes 实现的,那么您可以使用 Xania.AspNet.Simulator nuget 包,如下所示:
--
If you care about validation but you don't care about how it is implemented, if you only care about validation of your action method at the highest level of abstraction, no matter whether it is implemented as using DataAnnotations, ModelBinders or even ActionFilterAttributes, then you could use Xania.AspNet.Simulator nuget package as follows:
--
基于 @giles-smith 的答案和评论,对于 Web API:
请参阅上面的答案编辑...
Based on @giles-smith 's answer and comments, for Web API:
See on answer edit above...
@giles-smith 的答案是我的首选方法,但实现可以简化:
@giles-smith's answer is my preferred approach but the implementation can be simplified:
您还可以将 actions 参数声明为
FormCollection
,而不是传入BlogPost
。 然后您可以自己创建BlogPost
并调用UpdateModel(model, formCollection.ToValueProvider());
。这将触发对 FormCollection 中任何字段的验证。
只需确保您的测试为视图表单中要留空的每个字段添加一个空值即可。
我发现这样做,以几行额外的代码为代价,使我的单元测试更类似于在运行时调用代码的方式,从而使它们更有价值。 您还可以测试当有人在绑定到 int 属性的控件中输入“abc”时会发生什么。
Instead of passing in a
BlogPost
you can also declare the actions parameter asFormCollection
. Then you can create theBlogPost
yourself and callUpdateModel(model, formCollection.ToValueProvider());
.This will trigger the validation for any field in the
FormCollection
.Just make sure your test adds a null value for every field in the views form that you want to leave empty.
I found that doing it this way, at the expense of a few extra lines of code, makes my unit tests resemble the way the code gets called at runtime more closely making them more valuable. Also you can test what happens when someone enters "abc" in a control bound to an int property.
当您在测试中调用 homeController.Index 方法时,您没有使用任何触发验证的 MVC 框架,因此 ModelState.IsValid 将始终为 true。 在我们的代码中,我们直接在控制器中调用帮助器 Validate 方法,而不是使用环境验证。 我对 DataAnnotations 没有太多经验(我们使用 NHibernate.Validators),也许其他人可以提供如何从控制器内调用 Validate 的指导。
When you call the homeController.Index method in your test, you aren't using any of the MVC framework that fires off the validation so ModelState.IsValid will always be true. In our code we call a helper Validate method directly in the controller rather than using ambient validation. I haven't had much experience with the DataAnnotations (We use NHibernate.Validators) maybe someone else can offer guidance how to call Validate from within your controller.
我在测试用例中使用 ModelBinders 来更新 model.IsValid 值。
使用我的 MvcModelBinder.BindModel 方法如下(基本上使用相同的代码
在 MVC 框架内部):
I'm using ModelBinders in my test cases to be able to update model.IsValid value.
With my MvcModelBinder.BindModel method as follows (basically the same code used
internally in the MVC framework):