How can I test ModelState?

C#asp.net Mvc

C# Problem Overview


How can I test Controller.ViewData.ModelState? I would prefer to do it without any mock framework.

C# Solutions


Solution 1 - C#

You don't have to use a Mock if you're using the Repository Pattern for your data, of course.

Some examples: http://www.singingeels.com/Articles/Test_Driven_Development_with_ASPNET_MVC.aspx

// Test for required "FirstName".
   controller.ViewData.ModelState.Clear();

   newCustomer = new Customer
   {
       FirstName = "",
       LastName = "Smith",
       Zip = "34275",    
   };

   controller.Create(newCustomer);

   // Make sure that our validation found the error!
   Assert.IsTrue(controller.ViewData.ModelState.Count == 1, 
                 "FirstName must be required.");

Solution 2 - C#

//[Required]
//public string Name { get; set; }
//[Required]
//public string Description { get; set; }

ProductModelEdit model = new ProductModelEdit() ;
//Init ModelState
var modelBinder = new ModelBindingContext()
{
    ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(
                      () => model, model.GetType()),
    ValueProvider=new NameValueCollectionValueProvider(
                        new NameValueCollection(), CultureInfo.InvariantCulture)
};
var binder=new DefaultModelBinder().BindModel(
                 new ControllerContext(),modelBinder );
ProductController.ModelState.Clear();
ProductController.ModelState.Merge(modelBinder.ModelState);

ViewResult result = (ViewResult)ProductController.CreateProduct(null,model);
Assert.IsTrue(result.ViewData.ModelState["Name"].Errors.Count > 0);
Assert.True(result.ViewData.ModelState["Description"].Errors.Count > 0);
Assert.True(!result.ViewData.ModelState.IsValid);

Solution 3 - C#

For testing Web API, use the Validate method on the controller:

var controller = new MyController();
controller.Configuration = new HttpConfiguration();
var model = new MyModel();

controller.Validate(model);
var result = controller.MyMethod(model);

Solution 4 - C#

Ran into this problem for .NetCore 2.1 Here's my solution:

Extension Method

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace MyExtension
{
    public static void BindViewModel<T>(this Controller controller, T model)
    {
        if (model == null) return;
        
        var context = new ValidationContext(model, null, null);
        var results = new List<ValidationResult>();

        if (!Validator.TryValidateObject(model, context, results, true))
        {
            controller.ModelState.Clear();
            foreach (ValidationResult result in results)
            {
                var key = result.MemberNames.FirstOrDefault() ?? "";
                controller.ModelState.AddModelError(key, result.ErrorMessage);
            }
        }
    }
}

View Model

public class MyViewModel
{
    [Required]
    public string Name { get; set; }
}

Unit Test

public async void MyUnitTest()
{
    // helper method to create instance of the Controller
    var controller = this.CreateController();

    var model = new MyViewModel
    {
        Name = null
    };

    // here we call the extension method to validate the model
    // and set the errors to the Controller's ModelState
    controller.BindViewModel(model);

    var result = await controller.ActionName(model);

    Assert.NotNull(result);
    var viewResult = Assert.IsType<BadRequestObjectResult>(result);
}

Solution 5 - C#

This not only let's you check that the error exists but also checks that it has the exact same error message as expected. For example both of these parameters are Required so their error message shows as "Required".

Model markup:

//[Required]
//public string Name { get; set; }
//[Required]
//public string Description { get; set; }

Unit test code:

ProductModelEdit model = new ProductModelEdit() ;
//Init ModelState
var modelBinder = new ModelBindingContext()
{
    ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(
                      () => model, model.GetType()),
    ValueProvider=new NameValueCollectionValueProvider(
                        new NameValueCollection(), CultureInfo.InvariantCulture)
};
var binder=new DefaultModelBinder().BindModel(
                 new ControllerContext(),modelBinder );
ProductController.ModelState.Clear();
ProductController.ModelState.Merge(modelBinder.ModelState);

ViewResult result = (ViewResult)ProductController.CreateProduct(null,model);
Assert.IsTrue(!result.ViewData.ModelState.IsValid);
//Make sure Name has correct errors
Assert.IsTrue(result.ViewData.ModelState["Name"].Errors.Count > 0);
Assert.AreEqual(result.ViewData.ModelState["Name"].Errors[0].ErrorMessage, "Required");
//Make sure Description has correct errors
Assert.IsTrue(result.ViewData.ModelState["Description"].Errors.Count > 0);
Assert.AreEqual(result.ViewData.ModelState["Description"].Errors[0].ErrorMessage, "Required");

Solution 6 - C#

Adding to the great answers above, check out this fantastic use of the protected TryValidateModel method within the Controller class.

Simply create a test class inheriting from controller and pass your model to the TryValidateModel method. Here's the link: http://blog.icanmakethiswork.io/2013/03/unit-testing-modelstate.html

Full credit goes to John Reilly and Marc Talary for this solution.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioneKek0View Question on Stackoverflow
Solution 1 - C#Scott HanselmanView Answer on Stackoverflow
Solution 2 - C#VaSSaVView Answer on Stackoverflow
Solution 3 - C#Bart VerkoeijenView Answer on Stackoverflow
Solution 4 - C#Paul - Soura Tech LLCView Answer on Stackoverflow
Solution 5 - C#abovetempoView Answer on Stackoverflow
Solution 6 - C#Alex StephensView Answer on Stackoverflow