One of the most important aspect of TDD is writing unit tests that are more readable and maintainable. Lot of people fail with TDD because when they add more unit tests to the code, they don’t put the effort to make them more readable and maintainable. I have seen cases where adding an extra dependency to a class would mean that the developers needs to modify 100 tests where they were creating an instance of that class. This is a result of writing bad unit tests.

Focus on Arrange

Normally when developers write unit tests they follow the 3A pattern (Arrange, Act & Assert). It is critical that you put extra effort in making the Arrange part of your test more readable and maintainable. If you using some BDD tool, arrange will be equivalent to you Given section.

Create single point of object creation

Whenever I write tests, I always make sure that there is a single point of each object creation. The “new” keyword is your enemy when you are writing unit tests and you must make sure to use it only once per object if possible.

Consider a scenario where we have the following user class

    public class User
    {
        public String Name { get; set; }
        public String UserName { get; set; }
        public String Password { get; set; }

        public Boolean IsValidPassword()
        {
            // Validation logic goes here ...
        }
    }

User class has one method to validate the password. We can have multiple rules to validate a password such as it must be at least 6 characters long, it must not be null etc. You might write tests like these to validate that behavior

        [Test]
        public void IsValidPassword_PasswordIsLessThan6Characters_ShouldReturnFalse()
        {
            // Arrange
            // Given password is less than 6 characters
            var user = new User() { UserName = "Anything", Name = "Anything", Password = "12345" };

            // Act
            // When we validate the password
            var isValidPassword = user.IsValidPassword();

            // Assert
            // Then the result should be false
            isValidPassword.Should().BeFalse();
        }

        [Test]
        public void IsValidPassword_PasswordIsNull_ShouldReturnFalse()
        {
            // Arrange
            // Given password is less than 6 characters
            var user = new User() { UserName = "Anything", Name = "Anything", Password = null };

            // Act
            // When we validate the password
            var isValidPassword = user.IsValidPassword();

            // Assert
            // Then the result should be false
            isValidPassword.Should().BeFalse();
        }

Notice that in both unit tests, I am creating an instance of User class. In future, if I have to add any dependency to the User class, I would need to modify both these tests. To avoid this problem I always tend to create a single point for object creation.

 


        [Test]
        public void IsValidPassword_PasswordIsLessThan6Characters_ShouldReturnFalse()
        {
            // Arrange
            var user = A.User().Build();

            // Act
            // When we validate the password
            var isValidPassword = user.IsValidPassword();

            // Assert
            // Then the result should be false
            isValidPassword.Should().BeFalse();
        }

        public class A
        {
            public static UserBuilder User()
            {
                return new UserBuilder();
            }
        }

        public class UserBuilder
        {
            private readonly User _user = new User();

            public User Build()
            {
                return _user;
            }
        }

 

I have added a new class named “A”. It is simply a wrapper around the builder objects and make the test more readable. I am returning an instance of the UserBuilder class which will contain methods to create different variations of the user object later. Notice how my test has become much more readable now and if I am creating a User in all my tests using this Builder, than in future, if I need to modify the constructor of the User class, I will only need to make change in one place to build all the tests.

Creating variation of objects

Now let’s see how builder can help in creating different variations of object for testing. I need to test different variations of the password, I can use the builder to achieve that while keeping my tests very readable.

 

        [Test]
        public void IsValidPassword_PasswordIsLessThan6Characters_ShouldReturnFalse()
        {
            // Arrange
            // Given password is less than 6 characters
            var user = A.User().WithDefaultData().But().WithPassword("12345").Build();

            // Act
            // When we validate the password
            var isValidPassword = user.IsValidPassword();

            // Assert
            // Then the result should be false
            isValidPassword.Should().BeFalse();
        }

        [Test]
        public void IsValidPassword_PasswordIsNull_ShouldReturnFalse()
        {
            // Arrange
            // Given password is less than 6 characters
            var user = A.User().WithDefaultData().But().WithPassword(null).Build();

            // Act
            // When we validate the password
            var isValidPassword = user.IsValidPassword();

            // Assert
            // Then the result should be false
            isValidPassword.Should().BeFalse();
        }

    public class A
    {
        public static UserBuilder User()
        {
            return new UserBuilder();
        }
    }

    public class UserBuilder
    {
        private readonly User _user = new User();

        public User Build()
        {
            return _user;
        }

        public UserBuilder But()
        {
            return this;
        }

        public UserBuilder WithDefaultData()
        {
            _user.UserName = "Anything";
            _user.Name = "Anything";
            _user.Password = "AnyPassword";
            return this;
        }

        public UserBuilder WithPassword(String password)
        {
            _user.Password = password;
            return this;
        }
    }

Using the UserBuilder class, I can create objects in default state and then modify them according to the requirement of the tests. Methods are fluent so I can chain them to make object creation read like a sentence. I even encapsulate the mocking of the dependencies using this approach but that is topic for another article.

Summary

Using builder pattern in creation of objects in unit tests help us write readable and maintainable tests that found the foundation of the benefit of having them in our code base. Remember every line of code written is a liability and if our unit tests are not readable and maintainable then they are not adding any real value because developers will start ignoring them once they start causing too much pain. That is one of the biggest reason why people feel they are doing TDD but not getting the benefit from it.