asp.net-mvc MVC 验证的单元测试
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1269713/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Unit tests on MVC validation
提问by Matthew Groves
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?
当我在 MVC 2 Preview 1 中使用 DataAnnotation 验证时,如何测试我的控制器操作是否在验证实体时将正确的错误放入 ModelState 中?
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):
这是我认为应该通过但没有通过的失败单元测试(使用 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, shouldI be testing validation, and should I be testing it in this way?
我想除了这个问题,应该我来测试验证,并应在我这种方式测试它?
采纳答案by Maurice
Instead of passing in a BlogPostyou can also declare the actions parameter as FormCollection. Then you can create the BlogPostyourself and call UpdateModel(model, formCollection.ToValueProvider());.
除了传入 aBlogPost您还可以将 actions 参数声明为FormCollection。然后您可以创建BlogPost自己并调用UpdateModel(model, formCollection.ToValueProvider());.
This will trigger the validation for any field in the FormCollection.
这将触发对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);
}
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.
我发现这样做以增加几行代码为代价,使我的单元测试更类似于在运行时调用代码的方式,从而使它们更有价值。您还可以测试当有人在绑定到 int 属性的控件中输入“abc”时会发生什么。
回答by ARM
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).
讨厌死掉一个旧帖子,但我想我会添加我自己的想法(因为我刚刚遇到这个问题并在寻找答案时遇到了这篇文章)。
- Don't test validation in your controller tests. Either you trust MVC's validation or write your own (i.e. don't test other's code, test your code)
- If you do want to test validation is doing what you expect, test it in your model tests (I do this for a couple of my more complex regex validations).
- 不要在控制器测试中测试验证。要么你相信 MVC 的验证,要么自己写(即不要测试别人的代码,测试你的代码)
- 如果您确实想测试验证是否符合您的预期,请在您的模型测试中对其进行测试(我为一些更复杂的正则表达式验证执行此操作)。
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:
您真正想要在这里测试的是,当验证失败时,您的控制器会执行您期望它执行的操作。这是您的代码,也是您的期望。一旦您意识到这就是您想要测试的全部内容,测试就很容易了:
[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));
}
回答by Giles Smith
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.
我遇到了同样的问题,在阅读了 Pauls 的回答和评论后,我寻找了一种手动验证视图模型的方法。
I found this tutorialwhich explains how to manually validate a ViewModel that uses DataAnnotations. They Key code snippet is towards the end of the post.
我发现本教程解释了如何手动验证使用 DataAnnotations 的 ViewModel。他们的关键代码片段在帖子的末尾。
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.
我稍微修改了代码 - 在教程中,TryValidateObject 的第四个参数被省略(validateAllProperties)。为了使所有注释都进行验证,这应该设置为 true。
Additionaly I refactored the code into a generic method, to make testing of ViewModel validation simple:
另外,我将代码重构为通用方法,以使 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);
}
}
So far this has worked really well for us.
到目前为止,这对我们来说非常有效。
回答by Paul Alexander
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.
当您在测试中调用 homeController.Index 方法时,您没有使用任何触发验证的 MVC 框架,因此 ModelState.IsValid 将始终为真。在我们的代码中,我们直接在控制器中调用辅助 Validate 方法,而不是使用环境验证。我对 DataAnnotations(我们使用 NHibernate.Validators)没有太多经验,也许其他人可以提供指导如何从您的控制器中调用 Validate。
回答by Darren
I was researching this today and I found this blog postby 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.
我今天正在研究这个,我发现Roberto Hernández (MVP) 的这篇博文似乎提供了在单元测试期间为控制器操作触发验证器的最佳解决方案。这将在验证实体时在 ModelState 中放置正确的错误。
回答by ggarber
I'm using ModelBinders in my test cases to be able to update model.IsValid value.
我在我的测试用例中使用 ModelBinders 来更新 model.IsValid 值。
var form = new FormCollection();
form.Add("Name", "0123456789012345678901234567890123456789");
var model = MvcModelBinder.BindModel<AddItemModel>(controller, form);
ViewResult result = (ViewResult)controller.Add(model);
With my MvcModelBinder.BindModel method as follows (basically the same code used internally in the MVC framework):
使用我的 MvcModelBinder.BindModel 方法如下(基本上与 MVC 框架内部使用的代码相同):
public static TModel BindModel<TModel>(Controller controller, IValueProvider valueProvider) where TModel : class
{
IModelBinder binder = ModelBinders.Binders.GetBinder(typeof(TModel));
ModelBindingContext bindingContext = new ModelBindingContext()
{
FallbackToEmptyPrefix = true,
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(TModel)),
ModelName = "NotUsedButNotNull",
ModelState = controller.ModelState,
PropertyFilter = (name => { return true; }),
ValueProvider = valueProvider
};
return (TModel)binder.BindModel(controller.ControllerContext, bindingContext);
}
回答by Ibrahim ben Salah
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:
如果您关心验证但不关心它是如何实现的,如果您只关心在最高抽象级别上对 action 方法的验证,无论它是使用 DataAnnotations、ModelBinders 还是 ActionFilterAttributes 来实现,那么你可以使用 Xania.AspNet.Simulator nuget 包如下:
install-package Xania.AspNet.Simulator
--
——
var action = new BlogController()
.Action(c => c.Index(new BlogPost()), "POST");
var modelState = action.ValidateRequest();
modelState.IsValid.Should().BeFalse();
回答by Alex York
I agree that ARM has the best answer: test the behaviour of your controller, not the built-in validation.
我同意 ARM 有最好的答案:测试控制器的行为,而不是内置验证。
However, you can also unit test that your Model/ViewModel has the correct validation attributes defined. Let's say your ViewModel looks like this:
但是,您也可以单元测试您的 Model/ViewModel 是否定义了正确的验证属性。假设您的 ViewModel 如下所示:
public class PersonViewModel
{
[Required]
public string FirstName { get; set; }
}
This unit test will test for the existence of the [Required]attribute:
此单元测试将测试[Required]属性是否存在:
[TestMethod]
public void FirstName_should_be_required()
{
var propertyInfo = typeof(PersonViewModel).GetProperty("FirstName");
var attribute = propertyInfo.GetCustomAttributes(typeof(RequiredAttribute), false)
.FirstOrDefault();
Assert.IsNotNull(attribute);
}
回答by Albert
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:
与ARM相比,我没有挖坟的问题。所以这是我的建议。它建立在 Giles Smith 的回答之上,适用于 ASP.NET MVC4(我知道这个问题是关于 MVC 2,但谷歌在寻找答案时并没有区别对待,我无法在 MVC2 上进行测试。)而不是将验证代码放入一个通用的静态方法,我把它放在一个测试控制器中。控制器拥有验证所需的一切。因此,测试控制器如下所示:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Wbe.Mvc;
protected class TestController : Controller
{
public void TestValidateModel(object Model)
{
ValidationContext validationContext = new ValidationContext(Model, null, null);
List<ValidationResult> validationResults = new List<ValidationResult>();
Validator.TryValidateObject(Model, validationContext, validationResults, true);
foreach (ValidationResult validationResult in validationResults)
{
this.ModelState.AddModelError(String.Join(", ", validationResult.MemberNames), validationResult.ErrorMessage);
}
}
}
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:
当然,该类不需要是受保护的内部类,这就是我现在使用它的方式,但我可能会重用该类。如果某处有一个模型 MyModel 用漂亮的数据注释属性装饰,那么测试看起来像这样:
[TestMethod()]
public void ValidationTest()
{
MyModel item = new MyModel();
item.Description = "This is a unit test";
item.LocationId = 1;
TestController testController = new TestController();
testController.TestValidateModel(item);
Assert.IsTrue(testController.ModelState.IsValid, "A valid model is recognized.");
}
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.
希望能帮助到你。
回答by codeulike
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:
这并不能完全回答您的问题,因为它放弃了 DataAnnotations,但我会添加它,因为它可能有助于其他人为他们的控制器编写测试:
You have the option of not using the validation provided by System.ComponentModel.DataAnnotations but still using the ViewData.ModelState object, by using its AddModelErrormethod and some other validation mechanism. E.g:
您可以选择不使用 System.ComponentModel.DataAnnotations 提供的验证,但仍使用 ViewData.ModelState 对象,通过使用其AddModelError方法和其他一些验证机制。例如:
public ActionResult Create(CompetitionEntry competitionEntry)
{
if (competitionEntry.Email == null)
ViewData.ModelState.AddModelError("CompetitionEntry.Email", "Please enter your e-mail");
if (ModelState.IsValid)
{
// insert code to save data here...
// ...
return Redirect("/");
}
else
{
// return with errors
var viewModel = new CompetitionEntryViewModel();
// insert code to populate viewmodel here ...
// ...
return View(viewModel);
}
}
This still lets you take advantage of the Html.ValidationMessageFor()stuff that MVC generates, without using the DataAnnotations. You have to make sure the key you use with AddModelErrormatches what the view is expecting for validation messages.
这仍然可以让您利用Html.ValidationMessageFor()MVC 生成的东西,而无需使用DataAnnotations. 您必须确保您使用的密钥与AddModelError视图期望的验证消息相匹配。
The controller then becomes testable because the validation is happening explicitly, rather than being done automagically by the MVC framework.
然后控制器变得可测试,因为验证是显式发生的,而不是由 MVC 框架自动完成。
回答by malix
Based on @giles-smith 's answer and comments, for Web API:
基于@giles-smith 的回答和评论,对于 Web API:
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);
}
}
See on answer edit above...
请参阅上面的答案编辑...

