New to Testing Time-Dependent Code?... Use NodaTime.Testing!
When you’re just getting into testing and your app has a lot of time-dependent logic - you might feel like you’ve picked the wrong app to start testing with.
You could either:
- Give up and wait for the next app to start your testing practice
- Make some design choices that decouple your dependency on
DateTime.Now
For example, say you have a lot of classes that you want to stamp with a DateTime CreatedOn {get; set;}
property
To set this property, you assign it with DateTime.Now
in the constructor:
public class MyClass {
public IStock Stock { get; private set; }
public IMarketDay MarketDay { get; private set; }
public DateTime CreatedOn { get; }
public MyClass(IStock stock, IMarketDay marketDay) {
Stock = stock;
MarketDay = marketDay;
CreatedOn = DateTime.Now;
}
}
As of now, you don’t have much to test… but what if some behavior depended on the CreatedOn
property? Maybe you have some branching logic:
public class MyClass {
public IStock Stock { get; private set; }
public IMarketDay MarketDay { get; private set; }
public DateTime CreatedOn { get; }
public string thingState { get; private set; }
public MyClass(IStock stock, IMarketDay marketDay) {
Stock = stock;
MarketDay = marketDay;
CreatedOn = DateTime.Now;
if (marketDay.Date.ToDateTimeUtc() == CreatedOn.Date) {
DoThing1();
}
else {
DoThing2();
}
}
private void DoThing1() {
thingState = "Thing1 done!";
}
private void DoThing2() {
thingState = "Thing2 done!";
}
}
So how would we test this branching logic? Ideally, we want to find a way to decouple our class from its dependency on System.DateTime.Now
…
NodaTime’s FakeClock
There are ways to decouple System.DateTime.Now
without adding 3rd-party references (using a generic func delegate like here and here).
However… DateTime is flawed, so I prefer to use NodaTime because it makes me think about my unique concept of Time in my application.
NodaTime has a SystemClock
of its own. It exists as a Singleton that implements an IClock
interface. Conveniently, NodaTime.Testing offers FakeClock
for our testing needs. Here’s our new class with NodaTime instead of DateTime:
public class MyClassNodaTime {
public IStock Stock { get; private set; }
public IMarketDay MarketDay { get; private set; }
public Instant CreatedOn { get; private set; }
public string thingState { get; private set; }
public MyClassNodaTime(
IStock stock,
IMarketDay marketDay,
IClock clock) {
Stock = stock;
MarketDay = marketDay;
CreatedOn = clock.Now;
if (marketDay.Date.InUtc().Date == CreatedOn.InUtc().Date) {
DoThing1();
}
else {
DoThing2();
}
}
private void DoThing1() {
thingState = "Thing1 done!";
}
private void DoThing2() {
thingState = "Thing2 done!";
}
}
You can see that we are injecting any object that implements IClock
into our class. We are also using NodaTime Instant
now, which is not time-zone aware (because that’s not a requirement… yet).
Here’s a test of our branching logic which shouldn’t be in the class constructor:
[Fact]
public void when_on_a_market_day_do_thing1() {
var clock = new FakeClock(
Instant.FromUtc(2016, 5, 8, 0, 0));
var stock = new Stock();
var marketDay = new MarketDay(
Instant.FromUtc(2016, 5, 8, 0, 0));
var myClass = new MyClassNodaTime(stock, marketDay, clock);
Assert.Equal("Thing1 done!", myClass.thingState);
}
[Fact]
public void when_not_on_a_market_day_do_thing2() {
var clock = new FakeClock(
Instant.FromUtc(2016, 5, 8, 0, 0));
var stock = new Stock();
var marketDay = new MarketDay(
Instant.FromUtc(2016, 5, 8, 0, 0));
var myClass = new MyClassNodaTime(stock, marketDay, clock);
Assert.Equal("Thing2 done!", myClass.thingState);
}
Super Power
By the way, I snuck in a super power for you if you didn’t pick that up yet… We injected the dependency for IClock
… we are doing Dependency Injection!!!
This does wonders for the testability of your app. Without all the complexity of IoC Containers, etc. it’s actually easy to start using on your own.
Summary
NodaTime.Testing has a really handy implementation of its IClock
called FakeClock
that allows you to control the dependency of your app on SystemClock
.
NodaTime is the way to go when you’re doing complex stuff with Dates and Times because Dates and Times can be more complex than System.DateTime
leads you to believe.
Hope this helps you one day! Thanks for reading and be sure to sign up for my newsletter so you can get these tips sent right to your inbox!
Related Posts:
- Converting DateTimes by Offsets with NodaTime
- Intuitive Testing with Legacy Code
- How to Compare Object Instances in your Unit Tests Quickly and Easily
- Should You Jump into Unit Testing before OOP?
- How Working the ‘Vertical Slice’ Can Fix your Coding Mental Block
- Using TDD to Break Through ‘Paralysis by Analysis’
- Do I Really Have to Unit Test This Method?
Sources:
- Ayende Rahien. Dealing with time in tests
- James Michael Hare. C#/.NET Fundamentals: Unit Testing with Func
Generators - NodaTime Documentation. Unit testing with Noda Time
- Jon Skeet. Pluralsight: C# Design Strategies