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.
SystemWrapper
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.
But 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.
IDateTime
Here’s how SystemWrapper works for DateTime
.
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 TimeChecker
:
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());
Now ct.GetCurrentDateTime()
will return an IDateTime
containing the system clock time.
IFileInfo
For my recent Daily Summary File work, I used code like this in a method called DailySummary.WriteFile
:
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 System.Security.AccessControl
and 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)