Validation Application Block – Unit Test Validation Logic
The Enterprise Library Validation Application Block (VAB) is a great library for putting your validation in your business layer where it belongs rather than in the UI. It allows us to apply attributes to the properties of our business objects like this:
public class Person { [StringLengthValidator(1, 20, MessageTemplate="First Name must be between 1-20 characters.")] public string FirstName { get; set; } [StringLengthValidator(1, 20, MessageTemplate="Last Name must be between 1-20 characters.")] public string LastName { get; set; } }
But are you unit testing your validation code properly? Consider this test method:
[TestMethod] public void Person_Validation_Test() { var person = new Person(); person.FirstName = "Bill"; person.LastName = "Gates"; // Verify person is valid var validationResults = Validation.Validate(person); Assert.IsTrue(validationResults.IsValid, "Person should be valid."); // Now make first name invalid person.FirstName = string.Empty; validationResults = Validation.Validate(person); Assert.IsFalse(validationResults.IsValid, "Validation failed. FirstName should have been invalid."); // Now make last name invalid person.FirstName = "Bill"; person.LastName = string.Empty; validationResults = Validation.Validate(person); Assert.IsFalse(validationResults.IsValid, "Validation failed. LastName should have been invalid."); }
At first glance, this test method might look ok. It tests the person to make sure it's valid; it then makes FirstName invalid and then tests it; it then makes LastName invalid (while setting FirstName back to normal) and then tests it, etc. But actually, there is a long list of things that are wrong with that test method – do not unit test this way.
First off, a good unit test should only test ONE thing at a time. In other words, you should have a test method for the FirstName property, another for the LastName property, etc. In fact, you could have a test method for FirstName being string.Empty, another test method proving that FirstName is invalid when it is over 20 characters, etc. Additionally, trying to "reset" the FirstName property back to normal before testing the LastName validation is just asking for trouble. A test method constructed in this way is going to get monolithic and ultimately you're going to forget to "reset" properly. Additionally, you want to strive for DRY (Don't Repeat Yourself) in your unit tests and how many times do you want to "reset" various properties back to "normal"?
If that wasn't enough, there is something even more harrowing with this test. That is: after FirstName is set to string.Empty and validity checked for, how do we know that the FirstName property is the thing that is causing this object to be invalid? Sure, it looks obvious from the method but it's not explicit and it's uncertain. What if setting the FirstName property actually made some composite validator invalid but the FirstName itself was perfectly valid? What if, when we got to the LastName validation, we didn't remember to properly "reset" the FirstName property and we're asserting LastName is invalid where it was really the FirstName test that came before it that was making it invalid? There are just too many opportunities to make mistakes. To simplify all of this, the unit tests can be re-written like this:
[TestMethod] public void Person_FirstName_Is_Invalid() { var person = CreateValidPerson(); person.FirstName = string.Empty; person.AssertWithKey("FirstName"); } [TestMethod] public void Person_LastName_Is_Invalid() { var person = CreateValidPerson(); person.LastName = string.Empty; person.AssertWithKey("LastName"); } public static Person CreateValidPerson() { var person = new Person(); person.FirstName = "Bill"; person.LastName = "Gates"; ValidationResults validationResults = Validation.Validate(person); Assert.IsTrue(validationResults.IsValid, "Person should be valid."); return person; }
Notice that each test method is testing only one property (you can fill in additional test methods to ensure it's invalid when over 20 characters, etc.). Additionally, each test starts from a known, valid state by re-using the static CreateValidPerson() method (which, inside, Asserts that it is, in fact, valid). Most importantly, for each test, it uses the AssertWithKey() method to ensure that the reason it is invalid is, in fact, the property we're interested in. This AssertWithKey() is a simple extension method:
public static void AssertWithKey<T>(this T target, string keyToCheck) { ValidationResults validationResults = Validation.Validate(target); ValidationResult result = validationResults.FirstOrDefault(r => r.Key == keyToCheck); Assert.IsNotNull(result, "Validation Failed. {0} should have been invalid.", keyToCheck); }
Notice the use of the FirstOrDefault() extension method. The ValidationResults returned from the Validate() method is a collection which we can inspect to ensure that the key (the key is the property name) is tied to the validation result we are looking for and expecting – thus, ensuring that the test is in fact invalid because of the specific property we're testing.
The above code was written for MSTest but can be easily adapted to NUnit, xUnit, or your unit testing framework of choice.