Adam
Adam

Reputation: 3013

ASP MVC 3 testing controller calling ModelState.IsValid always returns true

I have an ASP MVC 3 application and in my Model I have implemented IValidatableObject.

When my controller posts for a create or edit, I obviously only want to save the model if it is valid.

I see many blogs and posts and answers that say something like

if(!ModelState.IsValid)
{
      return View();
}

My question. Why is it that ModelState.IsValid is always true in a unit test on the Controller?

Example:

[Test]
public void InValidModelsAreNotAdded()
{
    var invalidModel = new MyModel() { SomeField = "some data", SomeOtherField = "" };

    var result = _controller.Submit(invalidModel);

    _repository.AssertWasNotCalled(r => r.Add(Arg.Is.Anything));

}

Model code:


public class MyModel : IValidatableObject
{
    public string SomeField { get; set; }
    public string SomeOtherField { get; set; }

    public IEnumerable Validate(ValidationContext validationContext)
    {
        if(string.IsNullOrWhiteSpace(SomeOtherField))
        {
            yield return
                new ValidationResult("Oops invalid.", new[] {"SomeOtherField"});
        }
     }
}

The AssertWasNotCalled always fails this test.

I stepped through the test and noticed that the ModelState.IsValid is true for this test. It is as if the IValidatableObject.Validate is not being invoked. It seems to work when I run the project, but thats not much of a way to test drive an application.

Also, I realize I could use the [Required] attribute for my example, but my real code has much more complex validation to it.

Thoughts?

Upvotes: 19

Views: 7068

Answers (2)

Alfredo Cavalcanti
Alfredo Cavalcanti

Reputation: 527

Well, insted of simulate the model binding behavior you can do that:

public class YourController : Controller
{

    //some code

    public ViewResult someAction(Model model)
    {
        try
        {
            ValidateModel(model);
        }
        catch
        {
            // deal with errors
        }
    }

    //some code
}

ValidateModel with "try catch" blocks are much more readable for me. But you can still use "if" blocks with the method TryValidateModel

Hope that helps!!

Upvotes: 1

Craig Stuntz
Craig Stuntz

Reputation: 126587

It's true because you haven't called anything which sets it false.

This normally happens during binding, but since you just pass the model directly in the test you skip that altogether.

If you're trying to test validation, do that directly. If you're trying to test the error path in your controller, your test's arrange can call _controller.ModelState.AddModelError( //...

Upvotes: 23

Related Questions