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.

Win-Win?

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.