Unit Testing Custom Data Annotations

bug-chip.jpeg

Let me tell you, I’m really digging the latest version of ASP.NET MVC.  Aside from using Asynchronous controllers (which opened up a world of possibilities communicating between servers), I’ve recently stumbled on using Data Annotations in my models.  Both Brad Wilson and Phil Haack have written plenty of information to get you introduced to the concepts of using Data Annotations as a means of validation, so I’m not going to go into details on how to set all that up.  What I will tell you is that it saves tons of headache when it comes to validating input and reusing models.

When you have to validate a form of more than 3 fields, especially when those fields depend on one another, it can become a real pain to manage all of that code in multiple places.  Using Data Annotations solves that problem by putting all your validation directly in the model class via Attributes.  Now all your validation code is managed in one place.

What I really like about this is that you can write your own custom data annotations to do your own validating.  Now, there’s a lot of prebuilt ones provided by Microsoft.  But it doesn’t cover everything you’ll ever need.  Writing your own annotations is very simple, though.  Besides writing a well-formed attribute, your main concern is overriding the IsValid method.  It’s as easy as returning true or false.  You’re on your way.

Now, as much as it seems that I’m gushing over this concept, I did have one real problem with the idea.  You see, in the previous version of my project’s implementation, I have written several unit tests to make sure that the validation was working properly.  For example, there’s are at least 10 tests to make sure that a user’s phone number matches the format for that user’s country (i.e. NANP phone numbers for the US and Canada, International dialing code + phone number for non NANP countries).  I didn’t want to give up these tests.  I wanted to make sure that if another developer came in behind me and touched the code that our live deployment wouldn’t be out for the count while we tracked down the errors.

Brad Wilson does provide some notes on unit testing with data annotations.  However, his tests are mainly focused on making sure that the model is properly decorated with the annotations and that the controller properly responds to a model error.  There’s nothing in there to tell us how to test the annotations directly.

After hours of trying to Google my way out of this problem, I ran across a project on Codeplex that had the exact same issue.  And lucky me, they had it solved.  Foolproof is a project that introduces even more data annotations to the party.  The majority of them deal with comparing properties which was something I was already doing in my project.  What I found interesting was the way they are doing their Unit Tests.  In order to tests their attributes, they create a private instance of a dummy model which has been decorated with the attribute they want to test.  They then use that dummy model to test whether or not the attribute works by exposing an “IsValid” method of their own.  This method makes a call to the attribute code.  Presto!  A Unit test for an attribute.

So let’s get right to it then, shall we?  First, we need a base class for our dummy model. We’ll use this as a means to expose a method to test our attributes.

[csharp]
    internal abstract class ModelBase<T> where T : ValidationAttribute
    {
        public T GetClassAttribute()
        {
            return (T)this.GetType().GetCustomAttributes(typeof(T), false)[0];
        }

        public T GetPropertyAttribute(string property)
        {
            return (T)this.GetType().GetProperty(property).GetCustomAttributes(typeof(T), false)[0];
        }

        public bool IsValid()
        {
            var attribute = this.GetClassAttribute();
            return attribute.IsValid(this);
        }

        public bool IsValid(string property)
        {
            var attribute = this.GetPropertyAttribute(property);
            return attribute.IsValid(this.GetType().GetProperty(property).GetValue(this, null));
        }
    }
[/csharp]

I’ve done a few things with this class. First, note that it using a Generic syntax. That means that when we create our test model, we’ll provide it with the type of attribute we want to test. I’ve also included two types of attribute validation; one for validating class attributes and another for validating class properties.

Now that we have our base class for dummy models, we can write our Unit tests. I’ll start by writing a Unit test for the popular “PropertiesMustMatch” attribute. You can find its implementation in the default MVC2 project in the AccountModel.cs file. I prefer XUnit, but you can use any testing framework you like.

[csharp]
    public class PropertiesMustMatchAttributeTest
    {
        //Here's our test model class.  Note that we've decorated it with the attribute that we want to test
        //and that we've also inherited from the ModelBase.
        [PropertiesMustMatch("Value1", "Value2")]
        private class Model : ModelBase<PropertiesMustMatchAttribute>
        {
            public string Value1 { get; set; }
            public string Value2 { get; set; }
        }

        [Fact]
        public void AttributeReturnsTrueIfValuesMatch()
        {
            //Arrange
            var model = new Model() { Value1 = "foo", Value2 = "foo" };

            //Assert
            Assert.True(model.IsValid());
        }

        [Fact]
        public void AttributeReturnsFalseIfValuesDontMatch()
        {
            //Arrange
            var model = new Model() { Value1 = "foo", Value2 = "bar" };

            //Assert
            Assert.False(model.IsValid());
        }
    }
[/csharp]

Here, we’re testing a class property. I’ve created a private class within the Unit Testing class. This limits the use of this class to this Unit test rather than allowing other Unit tests in the project to make use of it. It’s super tiny, mainly because we only need to test whether or not two properties match. You’ll also notice that the Unit tests themselves are also really tiny (only 2 lines) which is also good because all we’re really trying to figure out is if the attribute returns true or false. You’ll find that when you run this, both tests should pass.

Here’s an example using a Property level Data Annotation. I copied the code from Phil Haack’s PriceAttribute. I decided I didn’t need to check whether or not the price ended in 99 cents, so I omitted that part of the code.

[csharp]
    public class MinPriceAttributeTest
    {
        private class Model : ModelBase<PriceAttribute>
        {
            [Price(MinPrice = 1.00)]
            public double Price { get; set; }
        }

        [Fact]
        public void AttributeReturnsTrueIfPriceIsGreaterThanOneDollar()
        {
            //Arrange
            var model = new Model() { Price = 1.01 };

            //Assert
            Assert.True(model.IsValid("Price"));
        }

        [Fact]
        public void AttributeReturnsFalseIfPriceIsLessThanOneDollar()
        {
            //Arrange
            var model = new Model() { Price = 0.00 };

            //Assert
            Assert.False(model.IsValid("Price"));
        }

        [Fact]
        public void AttributeReturnsTrueIfPriceIsOneDollar()
        {
            //Arrange
            var model = new Model() { Price = 1.00 };

            //Assert
            Assert.True(model.IsValid("Price"));
        }
    }
[/csharp]

Note that I’ve sent the name of the property along to the IsValid method. This hits those overloads that are meant to test Property annotations as opposed to Class annotations. These three tests also pass.

As you can see, Unit testing your attributes isn’t that hard at all. It involves a little creativity to be sure. But once you’ve got it down, you can be assured that your custom data annotations are indeed doing the validation that you want them to do. I’ve attached the sample code to this article, so feel free to download it and expand on it.

UnitTestingDataAnnotations.zip

8 thoughts on “Unit Testing Custom Data Annotations

  1. Pingback: How to Unit Test Custom Data Annotations « My Software Notes

  2. Beaumier167@gmail.com

    This is really a great weblog posting and very helpful. I really appreciate the research you put into it

    Reply
  3. Lance McNearney

    Nice work! Thanks for the write-up and finding a way to unit test custom attributes. Definitely helpful.

    On a side-note, your code examples are getting html-encoded.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>