Unit tests for checking MVC

How can I verify that my controller action puts the correct errors in ModelState when validating an object when I use 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 is the unsuccessful unit test, which, I think, should pass, but does not work (using MbUnit and 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 think, in addition to this question, should the test be tested, and should I test it this way?

+74
validation unit-testing tdd asp.net-mvc asp.net-mvc-2
Aug 13 '09 at 2:20
source share
12 answers

Instead of passing to BlogPost you can also declare the actions parameter as a FormCollection . Then you can create BlogPost yourself and call UpdateModel(model, formCollection.ToValueProvider()); .

This will trigger validation for any field in the 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 each view form field that you want to leave blank.

I found that doing it this way, with a few extra lines of code, makes my unit tests look like the code gets called at runtime, which makes them more valuable. You can also check what happens when someone enters "abc" into a control bound to an int property.

-3
Aug 13 '09 at 7:32
source share

Hatred of the necrome is an old post, but I thought I would add my own thoughts (since I had this problem and I came across this message when looking for an answer).

  • Do not check validation in controller tests. Either you trust the MVC check, or write your own (i.e. Do not check other code, check your code).
  • If you want to check the validity of the check, do what you expect, check it in your test tests (I do this for a few of my more complex regular expressions).

What you really want to check here is that your controller does what you expect from it when the check fails. This is your code and your expectations. Testing is easy if you realize that all you want to test is:

 [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)); } 
+187
Sep 28 '10 at 19:02
source share

I had the same problem, and after reading Pauls answer and comments, I was looking for a way to manually validate the view model.

I found this tutorial that explains how to manually validate a ViewModel that uses DataAnnotations. They have a key code fragment located at the end of the message.

I slightly changed the code - in the tutorial the 4th parameter TryValidateObject is omitted (validateAllProperties). In order to get all Validate annotations, this must be set to true.

Additionally, I reorganized the code into a general method to simplify the verification of the 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 very well for us.

+83
Jul 28 '10 at 13:08
source share

When you call the homeController.Index method in your test, you are not using any of the MVC frameworks that disables validation, so ModelState.IsValid will always be true. In our code, we call the helper Validate method directly in the controller, rather than using attestation. I did not have much experience with DataAnnotations (we use NHibernate.Validators), maybe someone else can offer guidance on calling Validate from your controller.

+6
Aug 13 '09 at 3:58
source share

I studied this today and I found this blog post by Roberto Hernandez (MVP), which seems to provide the best solution for running validators for controller action during unit testing. This will result in correct errors in the ModelState when validating the entity.

+3
Oct 05 '10 at 3:45
source share

I use ModelBinders in my test cases to be able to update the model.IsValid value.

 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 (basically the same code is used internally in the MVC environment):

  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); } 
+2
Feb 18 '10 at 19:50
source share

This does not answer your question because it refuses DataAnnotations, but I will add it because it can help other people write tests for their controllers:

You have the option not to use the validation provided by System.ComponentModel.DataAnnotations, but still using the ViewData.ModelState object, using its AddModelError method and another validation mechanism. For example:

 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 allows you to use the MVC generated Html.ValidationMessageFor() material without using DataAnnotations . You must ensure that the key you use with AddModelError matches the expected expectation of validation messages.

The controller then becomes verifiable because the verification is performed explicitly and not automatically performed using the MVC framework.

+1
Sep 23 '10 at 19:52
source share

I agree that ARM has a better answer: check the behavior of your controller, not the built-in check.

However, you can also unit test so that your model / ViewModel has the correct validation attributes. Let's say your ViewModel looks like this:

 public class PersonViewModel { [Required] public string FirstName { get; set; } } 

This unit test will check for the presence of the [Required] attribute:

 [TestMethod] public void FirstName_should_be_required() { var propertyInfo = typeof(PersonViewModel).GetProperty("FirstName"); var attribute = propertyInfo.GetCustomAttributes(typeof(RequiredAttribute), false) .FirstOrDefault(); Assert.IsNotNull(attribute); } 
+1
May 26 '12 at 16:41
source share

Unlike ARM, I have no problem digging seriously. So here is my suggestion. It is based on Giles Smith's answer and works in ASP.NET MVC4 (I know that it is about MVC 2, but Google makes no difference when searching for answers, and I cannot check for MVC2.) Instead of putting the verification code in general static method, I put it in a test controller. The controller has everything you need to check. So, the test controller looks like this:

 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, a class does not have to be a protected inner class, so I'm using it now, but I'm probably going to reuse this class. If somewhere there is a MyModel model that is decorated with beautiful attributes of data annotation, then the test looks something like this:

  [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 to test all my models and maybe expand it to make fun of the controller information a bit or use the protected methods that the controller has.

Hope this helps.

+1
Mar 21 '13 at 20:38
source share

If you care about validation, but you don’t care how it is implemented, if you only care about validating your action method at the highest level of abstraction, whether it is implemented as using DataAnnotations, ModelBinders or even ActionFilterAttributes, then you can use Xania.AspNet.Simulator nuget package as follows:

 install-package Xania.AspNet.Simulator 

-

 var action = new BlogController() .Action(c => c.Index(new BlogPost()), "POST"); var modelState = action.ValidateRequest(); modelState.IsValid.Should().BeFalse(); 
+1
Aug 01 '15 at 22:04
source share

Based on @ giles-smith answers and comments for the 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 the Editing Responses section above ...

0
Mar 30 '15 at 20:10
source share

@ The answer to giles-smith is my preferred approach, but the implementation can be simplified:

  public static void ValidateViewModel(this Controller controller, object viewModelToValidate) { 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); } } 
0
Feb 09 '17 at 8:42 on
source share



All Articles