views:

68

answers:

4

Let's say I have this unit test:

    [Test]
    public void LastNameShouldNotBeEmpty()
    {
        ExampleController controller = new ExampleController();

        Person editedPerson = new Person { FirstName = "j", LastName = "" };
        controller.EditPerson(editedPerson);

        Assert.AreEqual(controller.ModelState.IsValid, false);
    }

And this code:

public class ExampleController : Controller
{
    public ActionResult EditPerson(int personId)
    {
        // Serve up a view, whatever
        return View(Person.LoadPerson(personId));
    }

    [HttpPost]
    public ActionResult EditPerson(Person person)
    {
        if (ModelState.IsValid)
        {
            // TODO - actually save the modified person, whatever
        }

        return View(person);
    }
}

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

It's bothering me that if I TDD out a requirement that the LastName can't be empty, I can't satisfy the test using DataAnnotation attributes (the [Required] before the LastName declaration on Person) because when the controller's action method is invoked from a unit test, the MVC infrastructure hasn't gotten a chance to apply the validation it does during model binding.

(If I manually performed validation in the controller's EditPerson method, though, and added an error to the ModelState, that would be verifiable from a unit test.)

Am I missing something? I'd like to specify the validation behavior of my system using unit tests, but I'm not sure how to satisfy a unit test unless I abandon DataAnnotation attributes altogether and perform validation manually inside my controller's action methods.

I hope the intent of my question is clear; is there a way to force true model binding to execute (including its validation behavior, to test that I haven't forgotten important validation attributes) from an automated unit test?

Jeff

+1  A: 

I agree it's not a very satisfying situation. However, there are some simple workarounds:

  1. Work around this problem by reflecting on the data entities and looking for the necessary validation attributes (that's what I am currently doing). It's much easier than it may sound.

  2. Build your own validator that reflects the viewmodel parameter type and validates it. Use it to verify in your unit tests if the proper validation attributes are set. Assuming that your validation class is bug-free it should be equivalent to the validation algorithm in the ASP.NET MVC ModelBinder. I've written such a validator class for a different purpose and it's not much more difficult than the first option.

Adrian Grigore
+1  A: 

I personally believe you should have unit tests that test for the attributes themselves, outside of the scope of MVC. This should be part of your model tests, not your controller tests. You didn't write the MVC validation code, so don't try to test it! Just test the fact that your object has the right attributes you are expecting.

This is very rough, but you get the idea...

[Test]
public void LastNameShouldBeRequired()
{
    var personType = typeof(Person);
    var lastNamePropInfo = objType.GetProperty("LastName");
    var requiredAttrs = lastNamePropInfo.GetCustomAttributes(typeof(RequiredAttribute), true).OfType<RequiredAttribute>();
    Assert.IsTrue(requiredAttrs.Any());
}

Then in your MVC tests you just test the flow of a controller, not the validity of the data annotations. You can tell modelstate that it is invalid to test the flow of what happens if validation fails etc by adding an error manually, as you noted. Then it's a very controlled test of what your controller is responsible for, not what the framework is doing for you.

Jab
I see what you're saying, and I certainly don't have a need to test the proper behavior of the MVC infrastructure code. What I would be testing for is that I remembered to add the right attributes, not whether the infrastructure validates them properly (since I trust that it does). Your solution would work too. I would just prefer not to write radically different unit tests for validations that happen to be annotation-based, than for validations that happen to be implemented with custom logic in the action method. I came up with an alternative that lets me specify both tests the same way.
blaster
A: 

I don't like tests that check for the presence of attributes personally, it makes the tests act less like documentation and tightly coupled with my understanding of ASP.NET MVC (which may be wrong) and not tightly coupled with the business requirements (which I care about).

So for these kind of things, I end up writing integration tests, generating HTTP requests directly or via the browser with WatiN. Once you get this going you can write tests without the extra MVC abstraction, the tests document what you really care about being true. That said, such tests are slow.

I've also done something where my integration tests can make a backdoor request, which causes a test fixture to be loaded within the server process. This text fixture will temporarily override bindings in my IOC container... This reduces the setup of the integration tests, though they're only half-integration tests at that point.

I might, for instance, replace a controller with a mock controller that will verify the action method is called with the expected parameter. More usually I replace the site's data source with another data source that I've prepopulated.

Frank Schwieterman
+1  A: 

Here's one solution that I came up with. It requires that one line of code be added to the unit test, but I'm finding that it lets me not care whether validation is enforced via attributes for via custom code in the action method, which feels like the test is more in the spirit of specifying outcomes rather than implementation. It allows the test to pass as written even though the validation is coming from data annotations. Note that the new line is right above the invocation of the EditPerson action method:

    [Test]
    public void LastNameShouldNotBeEmpty()
    {
        FakeExampleController controller = new FakeExampleController();

        Person editedPerson = new Person { FirstName = "j", LastName = "" };

        // Performs the same attribute-based validation that model binding would perform
        controller.ValidateModel(editedPerson);

        controller.EditPerson(editedPerson);

        Assert.AreEqual(false, controller.ModelState.IsValid);
        Assert.AreEqual(true, controller.ModelState.Keys.Contains("LastName"));
        Assert.AreEqual("Last name cannot be blank", controller.ModelState["LastName"].Errors[0].ErrorMessage);
    }

ValidateModel is actually an extension method I created (the controller does have a ValidateModel method but it is protected so it can't be invoked from a unit test directly). It uses reflection to call the protected TryValidateModel() method on the controller, which will trigger the annotation-based validations as though the action method were truly being called through the MVC.NET infrastructure.

public static class Extensions
{
    public static void ValidateModel<T>(this Controller controller, T modelObject)
    {
        if (controller.ControllerContext == null)
            controller.ControllerContext = new ControllerContext();

        Type type = controller.GetType();
        MethodInfo tryValidateModelMethod =
            type.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).Where(
                mi => mi.Name == "TryValidateModel" && mi.GetParameters().Count() == 1).First();

        tryValidateModelMethod.Invoke(controller, new object[] {modelObject});
    }
}

It seems to work with minimal invasiveness, although there may be ramifications that I'm not aware of . . .

Jeff

blaster