Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC Controller post method unit test: ModelState.IsValid always true

I have written my first unit tests for an ASP.NET MVC web application. All works fine and it is giving me valuable information, but I can't test errors in the view model. The ModelState.IsValid is always true, even when some values are not filled in (empty string or null).

I have read already that the model validation happens when the posted data is mapped to the model and you need to write some code to do the model verification yourself:

  • Geek With Blogs
  • How can I test ModelState?

I have tried the three examples provided in the linked webpages, but it seems not to work for me.

Some code:

My viewmodel

... [Required(ErrorMessageResourceName = "ErrorFirstName", ErrorMessageResourceType = typeof(Mui))] [MaxLength(50)] [Display(Name = "Firstname", ResourceType = typeof(Mui))] public string FirstName { get; set; } ... 

The controller

...  [HttpPost]     public ActionResult Index(POSViewModel model)     {         Contract contract = contractService.GetContract(model.ContractGuid.Value);          if (!contract.IsDirectDebit.ToSafe())         {             ModelState.Remove("BankName");             ModelState.Remove("BankAddress");             ModelState.Remove("BankZip");             ModelState.Remove("BankCity");             ModelState.Remove("AccountNr");         }          if (ModelState.IsValid)         {             ...              contractValidationService.Create(contractValidation);             unitOfWork.SaveChanges();              return RedirectToAction("index","thanks");         }         else         {             return Index(model.ContractGuid.ToString());         }     } 

My unit test

  posViewModel.FirstName = null;   posViewModel.LastName = "";  ...  var modelBinder = new ModelBindingContext()         {             ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => posViewModel, posViewModel.GetType()),             ValueProvider = new NameValueCollectionValueProvider(new System.Collections.Specialized.NameValueCollection(), CultureInfo.InvariantCulture)         };         var binder = new DefaultModelBinder().BindModel(new ControllerContext(), modelBinder);         posController.ModelState.Clear();         posController.ModelState.Merge(modelBinder.ModelState);          ActionResult result = posController.Index(posViewModel);          //Assert         mockContractValidationService.Verify(m => m.Create(It.IsAny<ContractValidation>()), Times.Never);         Assert.IsInstanceOfType(result, typeof(ViewResult)); 

On the view, I'm using unobtrusive JavaScript validation, and it works.

like image 474
CyclingFreak Avatar asked Mar 21 '14 14:03

CyclingFreak


People also ask

Why ModelState IsValid is false in MVC?

IsValid is false now. That's because an error exists; ModelState. IsValid is false if any of the properties submitted have any error messages attached to them. What all of this means is that by setting up the validation in this manner, we allow MVC to just work the way it was designed.

How ModelState IsValid works in MVC?

ModelState. IsValid indicates if it was possible to bind the incoming values from the request to the model correctly and whether any explicitly specified validation rules were broken during the model binding process. In your example, the model that is being bound is of class type Encaissement .

How do you make a ModelState IsValid false?

AddModelError("Region", "Region is mandatory"); ModelState. IsValid will then return false.

How do I know if my ModelState is valid?

Below the Form, the ModelState. IsValid property is checked and if the Model is valid, then the value if the ViewBag object is displayed using Razor syntax in ASP.Net MVC.


2 Answers

You're trying to test two different things at the same time. The controller is not reponsible for validating the model state, only for behaving differently based on the result of that validation. So your unit tests for the controller shouldn't try to test the validation, that should be done in a different test. In my opinion you should have three unit tests:

  1. One that verifies whether model validation correctly
  2. One that validates whether the controller behaves correctly when modelstate is valid
  3. One that validates whether the controller behaves correctly when modelstate is invalid

Here's how you can do that:

1.Model validation

[Test] public void test_validation() {     var sut = new POSViewModel();     // Set some properties here     var context = new ValidationContext(sut, null, null);     var results = new List<ValidationResult>();     var isModelStateValid =Validator.TryValidateObject(sut, context, results, true);      // Assert here } 

2.Controller with invalid modelstate

[Test] public void test_controller_with_model_error() {     var controller = new PosController();     controller.ModelState.AddModelError("test", "test");      ActionResult result = posController.Index(new PosViewModel());      // Assert that the controller executed the right actions when the model is invalid } 

3.Controller with valid modelstate

[Test] public void test_controller_with_valid_model() {     var controller = new PosController();     controller.ModelState.Clear();      ActionResult result = posController.Index(new PosViewModel());      // Assert that the controller executed the right actions when the model is valid } 
like image 168
Kenneth Avatar answered Sep 19 '22 11:09

Kenneth


I found this solution: SO: Validation does not work when I use Validator.TryValidateObject combined with the solution @Kenneth provided:

[TestMethod]     public void test_validation()     {         var sut = new POSViewModel();         // Set some properties here         var context = new ValidationContext(sut, null, null);         var results = new List<ValidationResult>();         TypeDescriptor.AddProviderTransparent(new AssociatedMetadataTypeTypeDescriptionProvider(typeof(POSViewModel), typeof(POSViewModel)), typeof(POSViewModel));          var isModelStateValid = Validator.TryValidateObject(sut, context, results, true);          // Assert here     } 

If you have a class library with all you resources in, don't forget to reference it in your test project.

like image 22
CyclingFreak Avatar answered Sep 18 '22 11:09

CyclingFreak