Hone your craft: approaches to making code testable

I recently read a blog post about using shims to unit test without interfaces. It looks really compelling, so I thought I’d explore some ways to create testable code to determine if I like the idea of shims or not.

In this blog post, I’ll explore a couple of ways to turn a simple, yet complicated piece of c# code into something testable. While this code is written in c#, I would assume the principles apply universally to most object oriented langauges.

The task

Let’s begin with the beginning. Imagine you have to write code, that can determine if a date was yesterday.

The solution

This is the first version:

static void Main(string[] args)
{
    Console.WriteLine("Enter a date");
    DateTime.TryParse(Console.ReadLine(), out var date);
    Console.WriteLine($"WasYesterDay returned {(DateTime.Today - date).Days == 1}");
    Console.ReadLine();
}

Testing the solution

So, now I have a program, that takes a date as input and determines if the date was yesterday. This is easy to test manually. So let’s do that:

Enter a date
2018-03-18
WasYesterDay returned False
Enter a date
2018-03-20
WasYesterDay returned True

Great, the code works. But it would be better to write some automated tests rather than having to run all imaginable tests by hand.

Automating tests

Scripting

This could of course be done by writing some scripts, but we’ll skip this option and write some unit tests instead.

Unit testing

The first thing to do is to determine what to test. In this case the tricky thing is to determine if the entered date is yesterday, so this will be the focus of this test.

The code that prompts the user for input or the part that converts the input to a c# Date object is less interesting in this context.

To achieve testable greatness we first have to move the test-worthy code into a separate method/class, so the unit tests don’t depend on the input/output device being used:

public class DateService
{
    public static bool WasYesterDay(DateTime date)
    {
        return (DateTime.Today - date).Days == 1;
    }
}

static void Main(string[] args)
{
    Console.WriteLine("Enter a date");
    DateTime.TryParse(Console.ReadLine(), out var date);
    Console.WriteLine($"WasYesterDay returned {DateService.WasYesterDay(date)}");
}

Now it is possible to introduce some unit tests:

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestYesterday()
    {
        Assert.IsTrue(DateService.WasYesterDay(new DateTime(2018, 3, 20)));
    }

    [TestMethod]
    public void TestYesterdayEvening()
    {
        Assert.IsTrue(DateService.WasYesterDay(new DateTime(2018, 3, 20, 23, 0, 0)));
    }
}

An error

These unit tests will reveal a problem with the code. It doesn’t determine if the date was yesterday, rather it determines if the date and time was somewhere between 24 and 48 hours ago.

The solution

So a fix is in order:

public static bool WasYesterDay(DateTime date)
{
    return (DateTime.Today - date.Date).Days == 1;
}

Amazing, our automated test has already payed off.

A problem – tests will fail tomorrow

But there is a problem. For those tests to work, the computer running them has to have the date set to 2018-03-21. Otherwise the call to DateTime.Today inside WasYesterDay is going to return a date, that makes the tests fail.

Solutions

There are several ways to view this problem and several ways to solve it.

  • Don’t hardcode dates in unittests
  • Use shims to overwrite DateTime.Today
  • Make the code pure
  • Interfaces and DI
  • Introduce a DI framework
  • Use mocks

Let’s go through them one by one.

Don’t hardcode the dates in the testcode

You could argue that the code works like it should and that the problem is that the testcases provide a hardcoded date, while the method we are testing is using the current date.

We can solve this problem by not hardcoding the date in the testcode:

[TestMethod]
public void TestYesterday()
{
    Assert.IsTrue(DateService.WasYesterDay(DateTime.Today.AddDays(-1)));
}

[TestMethod]
public void TestYesterdayEvening()
{
    Assert.IsTrue(DateService.WasYesterDay(DateTime.Today.AddDays(-1).AddHours(23)));
}

This will work.

But I would prefer my testcases to not use data generated on the fly. While I don’t immediately see the problem here, it is just good practice to ensure that the unit tests are predictable and reproducable, and these are not, because everytime they are run, they get run with new input data.

Use shims to overwrite the call do DateTime.Today

Another way to solve the problem is to “hard-code” what date the WasYesterDay gets from DateTime.Today. For this purpose we’ll use Pose to create a shim, that intercepts the call to DateTime.Today and replaces it with a fixed value, when the test is run:

[TestClass]
public class UnitTest1
{
    private Shim _todayShim;

    [TestInitialize]
    public void Setup()
    {
        _todayShim = Shim.Replace(() => DateTime.Today).With(() => new DateTime(2018, 3, 21));
    }

    [TestMethod]
    public void TestYesterday()
    {
        bool? result = null;
        PoseContext.Isolate(() =>
            {
                result = DateService.WasYesterDay(new DateTime(2018, 3, 20));
            }, _todayShim);
        Assert.IsTrue(result != null && result.Value);
    }
    (...)
}

With this approach we managed to make the tests predictable and reproducable not changing the code in question. Unfortunately, the current incarnation of Pose.Shims makes it impossible to set breakpoints in the isolated code and Visual Studios Live Unit Tests get confused by it and reports false positives.

However, this solution works from the assumption, that the problem was with our testcode.

You could argue that the real cause of the problem was, that the DateService was not designed to be testable.

Make the code under test a pure function

The simplest way to solve the problem is to change the WasYesterDay method to be pure, meaning that the output is ONLY a result of the input with no dependencies to state (be it object state or the state of the universe). This approach is (for me at least) inspired by Mark Seemanns talks and blogposts about pure and functional programming e.g. the the From dependency injection to dependency rejection series.

In this case we have to remove the dependency on the universal space-time continuum introduced by the call to System.DateTime. Instead the caller to WasYesterDay has to provide both the current date and the date in question.

public class DateService
{
    public static bool WasYesterDay(DateTime date, DateTime now)
    {
        return (now.Date - date.Date).Days == 1;
    }
}

[TestClass]
public class UnitTest1
{
    private DateTime _now;

    [TestInitialize]
    public void Setup()
    {
        _now = new DateTime(2018, 3, 21);
    }

    [TestMethod]
    public void TestYesterday()
    {
        Assert.IsTrue(DateService.WasYesterDay(new DateTime(2018, 3, 20), _now));
    }

    [TestMethod]
    public void TestYesterdayEvening()
    {            
        Assert.IsTrue(DateService.WasYesterDay(new DateTime(2018, 3, 20, 23, 0, 0), _now));
    }
}

Now that the code is pure, it is much easier to write tests and the total code looks cleaner. It is also easy to test edge cases:

[TestMethod]
public void TestFarIntoTheFuture1()
{
    Assert.IsFalse(DateService.WasYesterDay(new DateTime(2018, 3, 20, 23, 0, 0), DateTime.MaxValue));
}

[TestMethod]
public void TestFarIntoTheFuture2()
{
    Assert.IsTrue(DateService.WasYesterDay(new DateTime(9999, 12, 30), DateTime.MaxValue));
}

The drawback however, is that the caller now has to provide two arguments to the functions instead of just one.

Console.WriteLine($"WasYesterDay returned {DateService.WasYesterDay(date, DateTime.Now)}");

Create an interface and use dependency injection

A more traditional approach in object oriented programming is to isolate the problematic dependency behind an interface and dependency inject different implementations of that interface depending on wether we are testing code or running it “for real”.

First the interface:

public interface IClockService
{
    DateTime Now { get; }
}
public class ClockService : IClockService
{
    public DateTime Now => DateTime.Now;
}

The DateService then needs to depend on IClockService, which means that WasYesterDay can no longer be a static method.

public class DateService
{
    private readonly IClockService _clockService;

    public DateService(IClockService clockService)
    {
        _clockService = clockService;
    }

    public bool WasYesterDay(DateTime date)
    {
        return (_clockService.Now.Date - date.Date).Days == 1;
    }
}

We use the interface in the application:

static void Main(string[] args)
{
    var dateService = new DateService(new ClockService());

    Console.WriteLine("Enter a date");
    DateTime.TryParse(Console.ReadLine(), out var date);
    Console.WriteLine($"WasYesterDay returned {dateService.WasYesterDay(date)}");
}

Then we create an alternative implementation for the tests:

class ClockServiceForTesting : IClockService
{
    public DateTime Now => new DateTime(2018, 3, 21);
}

And use it in the unit tests:

[TestClass]
public class UnitTest1
{
    private DateService _dateService;

    [TestInitialize]
    public void Setup()
    {
        _dateService = new DateService(new ClockServiceForTesting());
    }

    [TestMethod]
    public void TestYesterday()
    {
        Assert.IsTrue(_dateService.WasYesterDay(new DateTime(2018, 3, 20)));
    }
    (...)
}

So, there we have it, a solution that uses dependency injection.

Use a dependency injection framework

Using dependency injection usually also involves using a framework to resolve dependencies. Otherwise every piece of code calling DateService would have to manually resolve the dependency to IClockService.

Microsoft.Extensions.DependencyInjection to the rescue:

static void Main(string[] args)
{
    var serviceProvider = new ServiceCollection()
        .AddSingleton<IClockService, ClockService>()
        .AddSingleton<DateService>()
        .BuildServiceProvider();

    var dateService = serviceProvider.GetService<DateService>();

    Console.WriteLine("Enter a date");
    DateTime.TryParse(Console.ReadLine(), out var date);
    Console.WriteLine($"WasYesterDay returned {dateService.WasYesterDay(date)}");    
}

And of course the test code should not be considered a 2nd class Citizen:

[TestClass]
public class UnitTest1
{
    private ServiceProvider _serviceProvider;
    private DateService _dateService;
        
    [TestInitialize]
    public void Setup()
    {
        _serviceProvider = new ServiceCollection()
            .AddSingleton<IClockService, ClockServiceForTesting>()
            .AddSingleton<DateService>()
            .BuildServiceProvider();

        _dateService = _serviceProvider.GetService<DateService>();
    }

    [TestMethod]
    public void TestYesterday()
    {
        Assert.IsTrue(_dateService.WasYesterDay(new DateTime(2018, 3, 20)));
    }
    (...)
}

However, we might want more granular control in our unittests. Maybe we want to ensure that DateService calls the implementation of IClockService exactly once?

Use Mocks

Instead of writing our own implementation of IClockService we can use a mocking framework such as Moq to create a mock that implements IClockService and provides us more testing abilities.

[TestClass]
public class UnitTest1
{
    private readonly Mock<IClockService> _clockServiceMock = new Mock<IClockService>();
    private DateService _dateService;
        
    [TestInitialize]
    public void Setup()
    {
        _clockServiceMock.Setup(c => c.Now).Returns(new DateTime(2018, 3, 21));
        _dateService = new DateService(_clockServiceMock.Object);
    }

    [TestMethod]
    public void TestYesterday()
    {
        Assert.IsTrue(_dateService.WasYesterDay(new DateTime(2018, 3, 20)));
        _clockServiceMock.VerifyGet(c => c.Now, Times.Exactly(1));
    }
    (...)
}

This saves us the trouble of creating an implementation of IClockService and makes it easier to control the behavior of the IClockService implementation.

We can easily specify in a testcase that the property get’er should emulate edge-cases such as throwing an exception or return DateTime.MaxValue.

For instance we could check if the code still works as we approach the end of days:

[TestMethod]
public void TestFarIntoTheFuture1()
{
    _clockServiceMock.Setup(c => c.Now).Returns(DateTime.MaxValue);
    Assert.IsFalse(_dateService.WasYesterDay(new DateTime(2018, 3, 20)));
    _clockServiceMock.VerifyGet(c => c.Now, Times.Exactly(1));
}

[TestMethod]
public void TestFarIntoTheFuture2()
{
    _clockServiceMock.Setup(c => c.Now).Returns(DateTime.MaxValue);
    Assert.IsTrue(_dateService.WasYesterDay(new DateTime(9999, 12, 30)));
    _clockServiceMock.VerifyGet(c => c.Now, Times.Exactly(1));
}

In the example above it also gave us the ability to ensure that the implementation of IClockService.Now gets called exactly once. I would argue, however, that this is not necessarily a good thing. I’ve seen examples of unit tests that use all the possible features of Mocks to ensure nothing can change without some tests breaking. This effectively makes it impossible to refactor anything.

So while the extended testing abilities of mocks can be great they should be used with extreme caution, otherwise the unit tests can end up doing more damage than good.

Conclusion

I started writing the blog post, to explore the shims approach. On the face of it, it looks really cool. But after exploring and considering the options, I’m left with the impression that using shims for unit tests is the symptom of a problem (poorly designed code), not a solution to it.

By far the most normal solution is using interfaces, dependency injection and mocks.

Personally I’m inclined to prefer the pure function approach because the code becomes cleaner, simpler and smaller.

But what do you think? Let me know in the comments section below.

This Post Has 4 Comments

  1. Tomas Lycken

    > But after exploring and considering the options, I’m left with the impression that using shims for unit tests is the symptom of a problem (poorly designed code), not a solution to it.

    I tend to agree here – however, shims have an important place when trying to move an existing code base away from a poor design. It’s unfortunate but unavoidable that many of the code bases that are hardest to test because of problems like using DateTime.Now, are also the ones that are hardest to refactor – but it’s much less scary to refactor if you can specify the expected behavior in a couple of tests first.

    A shimming library like Pose is not something I’d use for testing in a greenfield project, but it’s a nice tool to have access to in brownfield situations, to make refactoring into a more testable design feel less intimidating.

  2. Logo Venture

    Thank you for share very nice knowledge. Your website is so coolI am impressed by the information that you have on this blog. It shows how well you understand this subject. Bookmarked this page, will come back for more. I found just the information I already searched everywhere and just couldn’t find. I like this site shown and it has given me some sort of desire to succeed for some reason, so keep up the good work

  3. Ladislau Radu Nagy

    Nice article!
    In this case I have only one issue with pure function, basically you move the issue up one level.
    Using interfaces, dependency injection and mocks, would be my preferred solution.

Leave a Reply