Testing Time - The Best of Both Worlds

I am a big fan of "the simplest thing that could possibly work". The elegance and simplicity of the following code is hard to overstate:

public static Func Now = () => DateTime.Now

It's wonderful because there are no external dependencies, no external libraries, and no versioning issues. Furthermore, we've placed a layer of indirection such that we can easily substitute calls to DateTime.[Utc]Now as necessary.

The only problem with this code is that it works at a single layer. If you were doing a full-blown integration test (and mocking time for some reason), you would have to ensure that all each individual library that had a dependency upon time had their own individual representation of "public static Func Now" properly set.

One possible solution would be…another layer of indirection. Specifically, during application wire-up and IoC configuration/setup, you simply re-assign the "Now" instance for each library (probably doing some kind of reflective search/wire-up) with something like the following:

public static Func Now = () => SystemTime.Now

Where "SystemTime" is a thread-local instance of time (if mocked), otherwise DateTime.[Utc]Now is called. Thread isolation is important because each thread can now have their own representation of time, if desired.


What does the above scenario give us?

  1. By utilizing the standard "Func Now = () => DateTime.Now", we have absolutely *zero* dependencies on external, non-BCL libraries. That's a huge win because we avoid versioning-related issues.
  2. By re-assigning the value of "Now" to SystemTime.Now during IoC wire-up/app startup/unit testing [SetUp], we have the ability to mock/fake time all the way down from the top layer/application-tier through to the bottom layer/infrastructure tier with a single statement, e.g. "using (SystemTime.Is(fakeTime)) { … }"

It feels like a win-win because only the host application's IoC container needs to be aware of this external dependency and we keep our application/library code as simple as possible.