Fun with literal extensions and Ambient Context by Mark Seemann
My book contains a section on the Ambient Context pattern that uses a TimeProvider as an example. It's used like this:
this.closedAt = TimeProvider.Current.UtcNow;
Yesterday I was TDDing a state machine that consumes TimeProvider and needed to freeze and advance time at different places in the test. Always on the lookout for making unit tests more readable, I decided to have a little fun with literal extensions and TimeProvider. I ended up with this test:
// Fixture setup var fixture = new WcfFixture(); DateTime.Now.Freeze(); fixture.Register(1.Minutes()); var sut = fixture.CreateAnonymous<CircuitBreaker>(); sut.PutInOpenState(); 2.Minutes().Pass(); // Exercise system sut.Guard(); // Verify outcome Assert.IsInstanceOfType(sut.State, typeof(HalfOpenCircuitState)); // Teardown
There are several items of note. Imagine that we can freeze time!
DateTime.Now.Freeze();
With the TimeProvider and an extension method, we can:
internal static void Freeze(this DateTime dt) { var timeProviderStub = new Mock<TimeProvider>(); timeProviderStub.SetupGet(tp => tp.UtcNow).Returns(dt); TimeProvider.Current = timeProviderStub.Object; }
This effectively sets up the TimeProvider to always return the same time.
Later in the test I state that 2 minutes pass:
2.Minutes().Pass();
I particularly like the grammatically correct English. This is accomplished with a combination of a literal extension and changing the state of TimeProvider.
First, the literal extension:
internal static TimeSpan Minutes(this int m) { return TimeSpan.FromMinutes(m); }
Given the TimeSpan returned from the Minutes method, I can now invoke the Pass extension method:
internal static void Pass(this TimeSpan ts) { var previousTime = TimeProvider.Current.UtcNow; (previousTime + ts).Freeze(); }
Note that I just add the TimeSpan to the current time and invoke the Freeze extension method with the new value.
Last, but not least, I should point out that the PutInOpenState method isn't some smelly test-specific method on the SUT, but rather yet another extension method.