Remove statics to make code unit testable

In this third part, we will look at why you need to remove static calls to make your code testable.

I have had discussions (read arguments) with several developers about why using static methods and variables can lead to non testable code. And why they are a well known code smell. Not all statics are a problem, but there are some pretty basic things us developers do which can cause problems.

Something as simple as using DateTime.Now can make your code impossible to test.

Imagine the following brief:

When a customer has been with us for 90 days, they qualify for a special prize. Create a function where we can pass in the date they signed up and return how many days remaining until they are eligible. If they are eligible return 0.

Now, being developers, we rush straight in and write the code. We will probably end up with something like this:

public class PrizeEligibilityChecker
{
	public int DaysUntilEligible(DateTime dateSignedUp)
	{
		var difference = (DateTime.Now - dateSignedUp).Days;
		if (difference >= 90) return 0;
		return 90 - difference;
	}
}

There you go boss! Done, and in record time!

R.I.P. Alan Rickman - Galaxy Quest

You come along and inherit this legacy code. Where do you start? I'd want to write some unit tests around this, but as it stands I can't because DateTime.Now is in there. If I can't control what the current date is then I can only anchor off when the test is being run. Less than Ideal...

Also, there is another issue. What happens if the company changes it's mind and want it to be 100 days? That never happens does it...

We have two places with the same value hard-coded!

To make this testable, the first thing I would want to do is refactor out those two dependencies.

We end up with:

public interface ICalendar
{
	DateTime Now { get; }
}

public class Calendar : ICalendar
{
	public DateTime Now=>DateTime.Now;
}

public class PrizeEligibilityChecker
{
	private readonly ICalendar _calendar;

	public PrizeEligibilityChecker(ICalendar calendar)
	{
		_calendar = calendar;
	}
	public int DaysUntilEligible(DateTime dateSignedUp, int eligibilityDays = 90)
	{
		var difference = (_calendar.Now - dateSignedUp).Days;
		if (difference >= eligibilityDays) return 0;
		return eligibilityDays - difference;
	}
}

Now we can add ICalendar to our IoC framework. I have also given the eligibility days a default value to avoid breaking code further down the line. There may be a need to make this another dependency in the future, but for now I'll leave that there.

The main thing is I haven't broken any functionality. Now we can add unit tests around this code. I will be using NUnit, so let's set up our test:

[TestFixture]
public class PrizeEligibilityTests
{
	[Test]
	public void ValidateResponse(DateTime now,DateTime dateSignedUp, int eligibilityDays, int expected)
	{
		//arrange
		var calendar = Substitute.For();
		calendar.Now.Returns(now);
		var prizeChecker = new PrizeEligibilityChecker(calendar);

		//act
		var output = prizeChecker.DaysUntilEligible(dateSignedUp);

		//assert
		output.Should().Be(expected);
	}
}

Here I will be passing in the required test cases and confirm the output against what I expect it to be.

The main thing I will be doing here is looking for edge cases. So given 90 days is the current code I would want to test 0, 89, 90, 91 days. I will also put in some tests to check that it still works if I change that 90 days.

Here is my test case source:

public static IEnumerable Data
{
	get
	{
		//0 Days
		yield return new object[] { new DateTime(2016, 10, 11), new DateTime(2016, 10, 11), 90, 90 };
		//89 Days
		yield return new object[] { new DateTime(2016, 10, 11), new DateTime(2016, 07, 14), 90, 1 };
		//90 Days
		yield return new object[] { new DateTime(2016, 10, 11), new DateTime(2016, 07, 13), 90, 0 };
		//91 Days
		yield return new object[] { new DateTime(2016, 10, 11), new DateTime(2016, 07, 12), 90, 0 };
		//Change to 30 Days - 0 Days
		yield return new object[] { new DateTime(2016, 10, 11), new DateTime(2016, 10, 11), 30, 30 };
		//Change to 30 Days - 29 Days
		yield return new object[] { new DateTime(2016, 10, 11), new DateTime(2016, 09, 12), 30, 1 };
		//Change to 30 Days - 30 Days
		yield return new object[] { new DateTime(2016, 10, 11), new DateTime(2016, 09, 11), 30, 0 };
		//Change to 30 Days - 31 Days
		yield return new object[] { new DateTime(2016, 10, 11), new DateTime(2016, 09, 10), 30, 0 };
	}
}

There are more edge cases, such as a date in the future, but for now we have covered off the main part of the test. If any issues arise then we can just add test cases to cover these off.

The last thing to do is add the test case source attribute. In full the code now looks like this:

public interface ICalendar
{
	DateTime Now { get; }
}

class Calendar : ICalendar
{
	public DateTime Now => DateTime.Now;
}

public class PrizeEligibilityChecker
{
	private readonly ICalendar _calendar;

	public PrizeEligibilityChecker(ICalendar calendar)
	{
		_calendar = calendar;
	}
	public int DaysUntilEligible(DateTime dateSignedUp, int eligibilityDays = 90)
	{
		var difference = (_calendar.Now - dateSignedUp).Days;
		if (difference >= eligibilityDays) return 0;
		return eligibilityDays - difference;
	}
}

[TestFixture]
public class PrizeEligibilityTests
{
	public static IEnumerable Data
	{
		get
		{
			//0 Days
			yield return new object[] { new DateTime(2016, 10, 11), new DateTime(2016, 10, 11), 90, 90 };
			//89 Days
			yield return new object[] { new DateTime(2016, 10, 11), new DateTime(2016, 07, 14), 90, 1 };
			//90 Days
			yield return new object[] { new DateTime(2016, 10, 11), new DateTime(2016, 07, 13), 90, 0 };
			//91 Days
			yield return new object[] { new DateTime(2016, 10, 11), new DateTime(2016, 07, 12), 90, 0 };
			//Change to 30 Days - 0 Days
			yield return new object[] { new DateTime(2016, 10, 11), new DateTime(2016, 10, 11), 30, 30 };
			//Change to 30 Days - 29 Days
			yield return new object[] { new DateTime(2016, 10, 11), new DateTime(2016, 09, 12), 30, 1 };
			//Change to 30 Days - 30 Days
			yield return new object[] { new DateTime(2016, 10, 11), new DateTime(2016, 09, 11), 30, 0 };
			//Change to 30 Days - 31 Days
			yield return new object[] { new DateTime(2016, 10, 11), new DateTime(2016, 09, 10), 30, 0 };
		}
	}

	[Test]
	[TestCaseSource(nameof(Data))]
	public void ValidateResponse(DateTime now, DateTime dateSignedUp, int eligibilityDays, int expected)
	{
		//arrange
		var calendar = Substitute.For();
		calendar.Now.Returns(now);
		var prizeChecker = new PrizeEligibilityChecker(calendar);

		//act
		var output = prizeChecker.DaysUntilEligible(dateSignedUp, eligibilityDays);

		//assert
		output.Should().Be(expected);
	}
}

The eagle eyed reader would instantly say "Wow, that's a lot more code!". In fact, that is one of the biggest comments I get from other developers when teaching unit testing.

Most of the extra code is the unit tests, but at no point have we increased the complexity of the code under test. The main thing this gives us is that we can now rely 100% on that piece of code. If we find any other edge cases which cause our application to do something a bit weird, we can just add another test. This will prevent the issue arising again. Assuming you run your test before deploying at least...

And the main thing we get out of this:

Look at all those green ticks. LOOK AT THEM!...

I don't know about you, but it gives me a warm fuzzy feeling inside knowing that my code does exactly what I told it to. Well, at least assuming I got my tests right...

The more you practice unit testing, the easier it becomes to find the dependencies as well as finding edge cases. And remember:

"Tested code is happy code! Every time you don't write tests for your code a puppy cries" - me

 

In this series:

  1. What is Unit Testing?
  2. My unit testing setup
  3. Remove statics to make code unit testable
  4. Allow me to mock your code