C# Unit Testing: Incrementally Improve Testability

Consider a seasoned brownfield (legacy or seasoned project; not new development) project with little to no unit tests.

How and where do you start unit testing? 

Should you stop all forward development and refactor the entire solution at once to be testable?  Probably not.

Most brown field projects were not developed with testing in mind so they will require many hours of refactoring to convert it all to be testable.  One big refactor represents a ton of risk.  Instead you can make small changes toward a better overall design that will be more testable in the long term and support adding unit tests incrementally going forward.

Mocking

Mocking is a pivotal capability which enables isolated testing down to a single unit of code (generally at the method or line level) even when other dependencies are used by that code.  There are many ways to mock but Moq is the C# mocking framework I use most often.  Moq can automatically create mocked objects to replace interfaces or virtual classes.  It’s super handy.

Mocking frameworks can generate fake objects that look and act like real dependencies.  The programmer/tester can control how the mocked objects behave in order to create different contexts to test against.  You could create these fake objects manually but it is a lot more work.

But what if you don’t have interfaces or virtual classes?

Abstraction

The primary mechanism we use to incrementally make code more testable is abstraction.  Let’s see how we might use abstraction to make code more testable.

Static Methods

Static method calls are common in older code bases because they are convenient.  You can use them everywhere without even instantiating a class.  For example,

    public class Helper
    {
        public static string ToUpper(string input)
        {
            //bug
            input = null;
            return input.ToUpper();
        }

    }
    public class WorkClass
    {
        public string CreateGreeting(string name)
        {
            var upperName = Helper.ToUpper(name);
            var timeOfDay = "morning";
            if (DateTime.Now.Hour > 12)
            {
                timeOfDay = "afternoon";
            }
            if (DateTime.Now.Hour > 17)
            {
                timeOfDay = "evening";
            }
            return string.Format("Good {0}, {1}.", timeOfDay, upperName);
        }
    }
public class Helper {
      public static string ToUpper(string input) {
          return input.ToUpper();
      }

}

public class WorkClass {
      public string CreateGreeting(string name) {
          var upperName = Helper.ToUpper(name);
          var timeOfDay = "morning";
          if (DateTime.Now.Hour > 12) {
              timeOfDay = "afternoon";
          }
          else if (DateTime.Now.Hour > 17) {
              timeOfDay = "evening";
          }
          return string.Format("Good {0}, {1}.", timeOfDay, upperName);
      }
}

ToUpper is a static method that is convenient to use but make the CreateGreeting method less testable.  These methods are simple but many times outside method calls are not. We should mock them so we can isolate and test only the code inside the CreateGreeting method.

A wrapper class is a simple type of abstraction that will allow us to isolate the code.  For example,


public class HelperWrapper : IHelper {
	public string ToUpper(string input) {
		return Helper.ToUpper(input);
	}
}
public interface IHelper {
	string ToUpper(string input);
}

public class WorkClass {
      
	IHelper _helper;
	public WorkClass() {
		_helper = new HelperWrapper();
	}
	
	public string CreateGreeting(string name) {
		var upperName = _helper.ToUpper(name);
		var timeOfDay = "morning";
		if (DateTime.Now.Hour > 12) {
			timeOfDay = "afternoon";
		}
		else if (DateTime.Now.Hour > 17) {
			timeOfDay = "evening";
		}
		return string.Format("Good {0}, {1}.", timeOfDay, upperName);
	}
}

Now we can use Moq to mock the IHelper interface and isolate this code.  When mocked the helper method implementation is not run so an error in that method wouldn’t cause this test to fail thereby avoiding false positive test failures for this test.

Check out the example source code and the bug in the Helper method.  The ShouldThrowException method shows the error in the real implementation.

DateTime.Now()

DateTime.Now() is another common static method used.  Similarly we can create an IDateProvider interface and DateProvider class to abstract this static method:

    public interface IDateProvider
    {
        DateTime GetNow();
    }
    public class DateProvider : IDateProvider
    {
        public DateTime GetNow()
        {
            return DateTime.Now;
        }
    }

Without mocking and this abstraction we wouldn’t be able to test the different times of day and the methods output.  With them we can:

        [Test]
        public void ShouldGreetAfternoon()
        {
            _mockDateProvider.Setup(x => x.GetNow()).Returns(new System.DateTime(2012, 1, 1, 13, 1, 1));

            Because();
            Assert.IsTrue(_result.Contains("afternoon"));
        }

If you have further questions or would like to share your experiences please leave a comment below and continue the conversation.

Happy Testing!

Leave a Reply