Mvc: TryValidateModel throws exception when unit tested

Created on 19 Nov 2015  路  10Comments  路  Source: aspnet/Mvc

When TryValidateModel is called in a controller, and then the controller is unit tested, the method throws exception. Tried with both beta8 and rc1. Project.json is the default one with referenced Microsoft.AspNet.Mvc, Microsoft.AspNet.Http and Xunit. In ASP.NET Web API 2, this was quite easy to test. Sample code:

Controller:

``` c#
public class HomeController : Controller
{
[HttpPost]
public IActionResult Index(SomeModel model)
{
this.TryValidateModel(model);

        if (this.ModelState.IsValid)
        {
            return Ok();
        }

        return HttpBadRequest();
    }
}

Model:

``` c#
public class SomeModel
    {
        [Required]
        public int Int { get; set; }

        [Required]
        [StringLength(20, MinimumLength = 5)]
        public string String { get; set; }
    }

Unit test:

``` c#
[Fact]
public void TryValidateModelTest()
{
// GetController is the same method used in the MVC source code tests
var controller = GetController(new GenericModelBinder(), new CompositeValueProvider());

        controller.Index(new SomeModel()); // throws null reference exception, test fails

        Assert.False(controller.ModelState.IsValid);
    }

Stacktrace of the failed test:

Test Name: ControllersTest.TryValidateModelTest
Test FullName: TestApp.Tests.ControllersTest.TryValidateModelTest
Test Source: c:\users\admin\documents\visual studio 2015\Projects\WebApplication2\src\ClassLibrary1\Class1.cs : line 48
Test Outcome: Failed
Test Duration: 0:00:00.075

Result StackTrace:
at Microsoft.AspNet.Mvc.ModelBinding.Validation.DataAnnotationsModelValidatorProvider.GetValidators(ModelValidatorProviderContext context)
at Microsoft.AspNet.Mvc.ModelBinding.Validation.ValidationVisitor.GetValidators(ModelMetadata metadata)
at Microsoft.AspNet.Mvc.ModelBinding.Validation.ValidationVisitor.ValidateNode()
at Microsoft.AspNet.Mvc.ModelBinding.Validation.ValidationVisitor.VisitSimpleType()
at Microsoft.AspNet.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model)
at Microsoft.AspNet.Mvc.ModelBinding.Validation.ValidationVisitor.VisitChildren(IValidationStrategy strategy)
at Microsoft.AspNet.Mvc.ModelBinding.Validation.ValidationVisitor.VisitComplexType()
at Microsoft.AspNet.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model)
at Microsoft.AspNet.Mvc.ModelBinding.Validation.ValidationVisitor.Validate(ModelMetadata metadata, String key, Object model)
at Microsoft.AspNet.Mvc.ModelBinding.Validation.DefaultObjectValidator.Validate(IModelValidatorProvider validatorProvider, ModelStateDictionary modelState, ValidationStateDictionary validationState, String prefix, Object model)
at Microsoft.AspNet.Mvc.Controller.TryValidateModel(Object model, String prefix)
at Microsoft.AspNet.Mvc.Controller.TryValidateModel(Object model)
at TestApp.Tests.HomeController.Index(SomeModel model) in c:\users\admin\documents\visual studio 2015\Projects\WebApplication2\src\ClassLibrary1\Class1.cs:line 23
at TestApp.Tests.ControllersTest.TryValidateModelTest() in c:\users\admin\documents\visual studio 2015\Projects\WebApplication2\src\ClassLibrary1\Class1.cs:line 51
Result Message: Object reference not set to an instance of an object.
```

Full example available HERE, HERE or HERE. Any pointers or suggestions?

3 - Done bug

Most helpful comment

You can pass your tests moking ObjectValidator.

            var objectValidator = new Mock<IObjectModelValidator>();
            objectValidator.Setup(o => o.Validate(It.IsAny<ActionContext>(), 
                                              It.IsAny<ValidationStateDictionary>(), 
                                              It.IsAny<string>(), 
                                              It.IsAny<Object>()));
            controller.ObjectValidator = objectValidator.Object;

All 10 comments

In particular, is there anything we need to do to make this more unit-test friendly?

We unnecessarily (attempt to) use MvcDataAnnotationsLocalizationOptions when there's no IStringLocalizerFactory. Product code fix is simple and I'll send it out shortly.

Removing Investigate label and putting this into RC2. @Eilon @danroth27 any objections?

Yeah sounds good.

6297822

I'm receiving this error when the controller is calling TryValidateModel during a unit test. What is the fix to this issue? I'm currently on the latest versions of ASPNET Core, .NET Core and Visual Studio.

@mbowman8 can you post a new issue with the specific code you have that's failing?

@Eilon issue #6000 has been created.

You can pass your tests moking ObjectValidator.

            var objectValidator = new Mock<IObjectModelValidator>();
            objectValidator.Setup(o => o.Validate(It.IsAny<ActionContext>(), 
                                              It.IsAny<ValidationStateDictionary>(), 
                                              It.IsAny<string>(), 
                                              It.IsAny<Object>()));
            controller.ObjectValidator = objectValidator.Object;

The mocking of ObjectValidator doesn't work for me.
I found a really useful extension method on Stackoverflow

Here the code

public static void ValidateViewModel<TViewModel, TController>(this TController controller, TViewModel viewModelToValidate)
            where TController : Controller
{
    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);
    }
}

add using to the extension methods and use like this:

var model = new MyModel();
controller.ValidateViewModel(model);

@m4ss1m0g this issue was closed long ago. If you cannot get something working, please open a new issue describing exactly what you're doing and what is broken.

Separately, your extension method does not use MVC except when it calls controller.ModelState.AddModelError(...). In particular, Validator.TryValidateObject(...) does _not_ call into the MVC validation system.

Was this page helpful?
0 / 5 - 0 ratings