Thursday 7 July 2016

Mocking the Current Time in C#

In my recent post “Testing 101 - A + B = C” I mentioned about mocking out calls to do with the current date & time to bring some determinism to those sorts of tests. I find this problem comes up any time you want to write tests around a thing that expires, or where you might be doing date arithmetic such as calculating someone’s current age.

I touched on my preferred solution somewhat tangentially back in 2011 when talking about a similar unit testing problem to do with the file-system (see “Unit Testing File-System Dependent Code”). However I decided it was worth writing it up explicitly as I’ve had a few conversations about it in the past and never been able to quickly point to something concrete.

A Level of Indirection

They say every problem in computer science can be solved with an extra level of indirection and that’s exactly what we need to do here. So, instead of using the DateTime.Now property everywhere:

var now = DateTime.Now;

…we instead use our own version:

var now = Clock.Now;

This might seem quite invasive, and in a sense it is, but historically to mock static methods involved specialist tools. If you just accept that you’re going to need to do this (and I’m sure you will if dates feature anywhere) you just do it from the beginning in a new project and it costs very little. Even retrofitting it later is easy as you can do it piecemeal if needs be, at least, to code that you own that is.

So, the default implementation can just chain onto the original property:

public static class Clock
{
  public static DateTime Now
  {
    get { return DateTime.Now; }
  }
}

With the abstraction in place we are now free to re-implement it so that we can replace the underlying behaviour to do whatever we want, such as return a fixed value in a test.

First we make the underlying function configurable by introducing another property to hold a delegate that we’ll invoke when Now is called [1]:

public static DateTime Now
{
  get { return NowImpl(); }
}

internal static Func<DateTime> NowImpl = () =>
{
  return DateTime.Now;
}

Naturally the default implementation does the right thing so that the production code will work out-of-the-box. However in a test we can now override it:

[Test]
public void the_current_time_is_always_fixed()
{
  var millenium = new DateTime(2000, 1, 1);

  Clock.NowImpl = () =>
  {
    return millenium;
  }

  Assert.That(Clock.Now, Is.EqualTo(millenium));
}

And that’s pretty much it. Normally I’ll add a Reset() method that can be called in the [TearDown] handler to put back the real clock just to keep things tidy and avoid a fixed date leaking out into another test.

Another Level of Indirection

You probably noticed that I marked the property that injects the mock function “internal” so that it has limited scope. I normally use the [InternalsVisibleTo] attribute to grant access to the test assembly. In the scenario where I can’t do that, such as when the Clock class resides in a shared assembly that is non-project specific (i.e. I can’t use [InternalsVisibleTo] to name an unknown assembly) I resort to a different trick.

Whilst I could give the NowImpl property a really log-winded name to discourage a developer from using it by accident, I prefer to keep the implementation private and then add a separate public Test API class to grant the necessary access. In C# a nested class has access to it’s parent’s internals and so I can do this instead:

private static Func<DateTime> NowImpl = () =>
{
  return DateTime.Now;
}

public static class TestApi
{
  public static Func<DateTime> Now
  {
    set { NowImpl = value; }
  }

  public void Reset()
  {
    NowImpl = () { return DateTime.Now; }
  }
}

This doesn’t really grant any more protection to the Clock internals but it does mean that IntelliSense won’t show up the other test methods unless you navigate into the TestApi class which feels a little cleaner.

Consequently the test would now look like this:

[TearDown]
public void ResetClock()
{
  Clock.TestApi.Reset();
}

[Test]
public void the_current_time_is_always_fixed()
{
  var millenium = new DateTime(2000, 1, 1);

  Clock.TestApi.Now = () =>
  {
    return millenium;
  }

  Assert.That(Clock.Now, Is.EqualTo(millenium));
}

 

[1] I could have collapsed the lambda down to a simple method group, but I prefer to make lambdas look like actual method bodies when explaining things. ReSharper will happily provide the relevant hint when you do it in real life.

No comments:

Post a Comment