In the last part Designing Application Using Test Driven Development we started our application design using test driven development. We created the Person and Address classes and wrote tests for it. In this article we will learn how the tests will serve as a guardian angel and keep us alert for every bad design decision.

Introduction:

In the last part Designing Application Using Test Driven Development we started our application design using test driven development. We created the Person and Address classes and wrote tests for it. In this article we will learn how the tests will serve as a guardian angel and keep us alert for every bad design decision.

The Illusion of Successful Test:

In the next test we are going to update the fields of the Person object and ensure that the fields are being updated. Take a look at the following test:

[Test]
        [RollBack]       
        public void should_be_able_to_update_person_successfully()
        {
            Person person = new Person() { FirstName = "Mohammad", LastName = "Azam", DOB = DateTime.Parse("02/09/1981") };
            person.Save();

            Person vPerson = Person.GetById(person.Id);
            vPerson.FirstName = "Mohammad1";
            vPerson.LastName = "Azam1";
            vPerson.DOB = DateTime.Parse("02/10/2000");

            Console.WriteLine(vPerson.DOB);

            vPerson.Save();

            Person uPerson = Person.GetById(vPerson.Id);
            Assert.AreEqual(vPerson.FirstName, uPerson.FirstName,"First name not updated!");
            Assert.AreEqual(vPerson.LastName, uPerson.LastName,"Last name not updated!");
            Assert.AreEqual(vPerson.DOB, uPerson.DOB,"dob is not being updated!");
        }

If you run the above test it will pass and gives you the illusion that everything is fine. But this test is not correct as you can see we are setting the age to “02/10/2000” which is definitely less than 18. Let’s check out the code for DOB property and see what is going on:

  public DateTime DOB
        {
            get { return _dob; }
            set {

                if ((DateTime.Now.Subtract(value).Days / 365) >= LEGAL_AGE)
                {
                    _dob = value;
                }        
            }
        }
 
The problem with the DOB property is that it is only set when the age is in the correct range otherwise it is ignored. This means that the incorrect age “02/09/2000” will never be set as it is invalid and DOB will always contains the “02/09/1981” age. Now, let’s think what is the minimum amount of code you can write to solve this problem. Here is the new implementation of the DOB property.

public DateTime DOB
        {
            get { return _dob; }
            set {


                // the person must be 18 or older!
                if ((DateTime.Now.Subtract(value).Days / 365) < LEGAL_AGE)
                    throw new ArgumentException("Invalid Age!! Age must be >= 18");

                _dob = value;  
        
            }
        }

Now, if the age is less than 18 an ArgumentException is thrown which will fail the test. Run the test and you will see that ArgumentException is fired and the test failed.

ArgumentException might feel like a very general exception. It is a good idea to throw custom exceptions that expresses the reason for the exception. The download sample includes the “InvalidAgeException” class which is triggered whenever an invalid age is encountered.
 
Refactoring:

Unit Testing is not only about making the tests pass but it is also about refactoring your code. Refactoring is a technique to make the code more readable without changing the workings of the code. Let’s see where we can apply refactoring principles.

Both the Person and Address objects contain the property “Id”. It is wise to put the “Id” property into a base class and have the Person and Address inherit from that base class.

public class BusinessBase
    {
        private int _id;
      

        public int Id
        {
            get { return _id; }
            internal set { _id = value; }
        }      

    }

  public class Person : BusinessBase

public class Address : BusinessBase

Each time you are done refactoring run the tests again to make sure you have not broken anything.

Building the Web User Interface:

It is time to see our application live using the web user interface. Let’s make a page to insert a Person. Here is the screen shot of the user interface to add a Person.

 

Nothing too fancy! If you do not type anything in the TextBoxes and click the button you will get the following message:

String was not recognized as a valid DateTime.

This means that the DOB TextBox expects a string that can be recognized by the DateTime Object. You can type any valid Date into the DOB TextBox and press the “Add Person” button again. Now, you will be greeted by the following error message:

Prepared statement '(@FirstName nvarchar(4000),@LastName nvarchar(4000),@DOB datetim' expects parameter @FirstName, which was not supplied.

The user will certainly not understand the error message and will contact your boss to get the explanation (This is not good!).

At this point we realize that we must have a better way to handle and display error messages to the user. This requires the implementation of Validators and Custom Attributes.

Implementing Custom Attributes and Validators:

Currently, we are handling the constraints in our object properties. Take a look at the following property which makes sure that the string is not null or empty.

public string FirstName
        {
            get { return _firstName; }
            set
            {
                if (!String.IsNullOrEmpty(value.Trim()))
                {

                    _firstName = value;
                }
            }
        }

There is nothing wrong with the above code and it will work as expected. The only problem is the code is repetitive in most of the string properties. Wouldn’t it be nice to have the constraint for not null or empty like this:

  [NotNullOrEmpty(“FirstName cannot be null or empty”)]
        public string FirstName
        {
            get { return _firstName; }
            set
            {
  _firstName = value;
            }
        }

Much simpler and easy to read and maintain.

Unit Testing Custom Attributes and Validators:

Let’s start by writing tests for our custom attributes and validators. All these tests will be stored in a separate file named “TestValidator.cs”. Here is the first test that makes sure that the Person object is invalid and has two broken rules.


[Test]
        public void should_get_broken_rules_for_not_null_or_empty_string()
        {
            Person person = new Person() { FirstName = "", LastName = "" };
            Assert.IsTrue(person.IsValid() == false);
            Assert.AreEqual(2, person.BrokenRules.Count);
        }

Off course, the above code would not compile since there is no “IsValid” method and BrokenRules property.

The IsValid method performs the validation on the object. There are couple of ways to implement the IsValid method. You can place a virtual IsValid method inside the BusinessBase class and override it in the inherited classes. You can also make a separate class “ValidationEngine” whose main purpose is to validate the objects. It is up to you to decide what best suits your needs.

We are going to use the person.IsValid() format. The statement person.IsValid() clearly indicates if the Person is valid or not. The validity is checked on the instance and not on the type. For this reason we implemented the IsValid method as an Extension Method which extends the BusinessBase class. Take a look at the implementation below:

public static class ExtensionMethods
    {
        public static bool IsValid(this BusinessBase item)
        {
            PropertyInfo[] properties = item.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);

            foreach (PropertyInfo property in properties)
            {
                object[] customAtt = property.GetCustomAttributes(typeof(ValidationAttribute), true);

                foreach (object att in customAtt)
                {
                    ValidationAttribute valAtt = att as ValidationAttribute;
                    if (valAtt != null)
                    {
                        if (!valAtt.IsValid(property.GetValue(item, null)))
                        {
                            item.BrokenRules.Add(new ValidationExceptionDetail() { PropertyName = property.Name, Message = valAtt.Message });
                        }
                    }
                }

            }

           return (item.BrokenRules.Count == 0);
        }
    }


The extension method “IsValid” takes a single parameter of object type BusinessBase. This means that all the objects which inherited from BusinessBase class will be able to use the IsValid method.

The BrokenRules list is defined in the BusinessBase class and holds the list of exceptions.

Here is the implementation of the NotNullOrEmptyAttribute:

[AttributeUsage(AttributeTargets.Property)]
    public class NotNullOrEmptyAttribute : ValidationAttribute
    {     

        public NotNullOrEmptyAttribute(string message)
        {
            Message = message;
        }

        public override bool IsValid(object item)
        {
            if (String.IsNullOrEmpty(item as String)) return false;

            return true;
        }
    }

Take a look at the code below which shows the attribute decorated on the properties.

[NotNullOrEmpty("First name cannot be null or empty")]
        public string FirstName
        {
            get { return _firstName; }
            set
            {
                _firstName = value;
            }
        }
       
        [NotNullOrEmpty("Last name cannot be null or empty")]
        public string LastName
        {
            get { return _lastName; }
            set
            {
                _lastName = value;
            }
        }

In the code above we used custom attributes to handle the constraints on the object’s property. Custom attributes allows the code to be more readable and easy modifiable. You can also create your own custom attributes library and add it in any new project that requires this behavior. 

We also need to make sure that the object is only persisted when valid. This is not a big task and can be solved by placing a single line of code:

public void Save()
        {
            if (!this.IsValid()) return;

            // this means adding a new person
            if (this.Id <= 0)
            {
                SQLDataAccess.SavePerson(this);
            }

            else
            {
                SQLDataAccess.UpdatePerson(this);
            }
        }


Displaying Errors on User Interface:

With custom attributes, validators and brokenrules list intact we can easily display errors on the user interface. All the errors are contained inside the BrokenRules list. All we need to do is to bind the list to a control which simply displays the list. We will use ASP.NET Repeator since it is the lightest data bound control. Take a look at the HTML code for the Repeator control.

    <asp:Repeater ID="repErrors" runat="server">
   
    <HeaderTemplate>
     ERRORS
    </HeaderTemplate>
   
    <ItemTemplate>
    <div>
    <%# Eval("PropertyName") %> : <%# Eval("Message") %>
    </div>
    </ItemTemplate>
   
    </asp:Repeater>
 
And here is the code behind for the “Add Person” button click event.

protected void AddPerson_Click(object sender, EventArgs e)
        {
          
                Person person = new Person() { FirstName = txtFirstName.Text, LastName = txtLastName.Text, DOB = DateTime.Parse(txtDOB.Text) };
                person.Save();

                this.repErrors.DataSource = person.BrokenRules;
                repErrors.DataBind();

        }

 

Since, the Repeator should be available for all the pages it would be a wise decision to put it in the Master Page or a User Control.

Conclusion:

In this article we learned that writing unit tests will help us change our modules without disturbing the rest of the application. We will feel more confident about our code. In the next part we will write some more tests and create the user interface for our web application.

[Download Sample]