This is one in a series of articles about Time Tortoise, a Universal Windows Platform app for planning and tracking your work schedule. For more on the development of this app and the ideas behind it, see my Time Tortoise category page.
An ongoing challenge when writing unit tests is isolating the class under test from the classes that it depends on. When you’re the one writing the dependencies, you can write them in a way that is friendly to unit testing. But when the dependencies are part of the framework you’re using, you have less control over the design. For .NET programmers who value unit testing, there’s a library called SystemWrapper whose purpose is to make .NET Framework apps more testable.
The .NET Framework is missing some features related to unit testing. In particular, not every class implements an interface. A simple example of this is
DateTime. As I have written a few times this year, it’s difficult to unit test code that depends on
DateTime.Now, since the current time is always changing, and unit tests need fixed values that they can assert against.
The solution: write your code to depend on an
IDateTime interface, rather than directly on
DateTime. Unfortunately, the .NET Framework doesn’t offer an
IDateTime interface. So I created one earlier this year, and that’s what I’ve been using for unit testing.
DateTime is just one class. The .NET Framework has numerous classes with the same problem. That’s where SystemWrapper comes in. The maintainers of SystemWrapper keep an eye on changes to the Framework, and update their project to keep in sync with it. So if you need to test code that depends on a .NET class, you may be able to find the appropriate interface in SystemWrapper.
Here’s how SystemWrapper works for
Consider a class called
TimeChecker that accepts a date/time provider of type
IDateTime, and exposes a public method called
GetCurrentDateTime that return the current date/time according to that provider.
SystemWrapper offers the
IDateTime interface, and a class called
DateTimeWrap that implements that interface. Here’s how a unit test could use
IDateTime and Moq to test
var dateTime = new Mock<IDateTime>(); dateTime.Setup(d => d.Now). Returns(new DateTimeWrap(new DateTime(2017, 10, 21))); var ct = new TimeChecker(dateTime.Object); Assert.Equal(new DateTime(2017, 10, 21), ct.GetCurrentDateTime().DateTimeInstance);
Moq can create a mock object based on an interface. In this case, we create a date/time provider that always thinks it’s October 21, 2017. Then we can use that date to verify that
GetCurrentDateTime works correctly.
When we need a real date/time provider, we can create a
DateTimeWrap using its default constructor:
var ct = new TimeChecker(new DateTimeWrap());
ct.GetCurrentDateTime() will return an
IDateTime containing the system clock time.
For my recent Daily Summary File work, I used code like this in a method called
var fileInfo = new FileInfo(fullName); fileInfo.Directory.Create();
This isn’t ideal for unit testing, since it creates a real directory on the real disk. Unit tests shouldn’t write to disk, since that slows them down and leaves artifacts (files and directories on disk) when the tests are complete.
To avoid this,
WriteFile should depend on an
IFileInfo interface rather than the concrete
FileInfo class. But System.IO.FileInfo doesn’t implement an interface. Fortunately, SystemWrapper has an
IFileInfo interface and a
FileInfoWrap class that implements it using System.IO.FileInfo.
As I was preparing to update my
DailySummary class, I ran into a problem: SystemWrapper uses .NET Framework 4.5. But Time Tortoise (as of last week) is based on .NET Standard 1.4, so it can’t reference a net45 assembly.
I thought about trying to create a .NET Standard version of SystemWrapper, but quickly found out what a large project that would be. For example, when I added
IFileInfo.cs to a .NET Standard class library project, I immediately got errors about the
SystemInterface.Security.AccessControl namespaces, which don’t exist in .NET Standard (even in v2.0).
As APIs continue to be added to .NET Standard, a time may come when the differences between .NET Standard and .NET Classic are small enough that SystemWrapper can be converted with minimal effort. But for now, SystemWrapper will have to serve merely as a source of code examples, rather than as a library I can directly reference from Time Tortoise.
(Image credit: Nate Grigg)