Red-Green-Code

Deliberate practice techniques for software developers

  • Home
  • About
  • Contact
  • Project 462
  • CP FAQ
  • Newsletter

Time Tortoise: Using SystemWrapper for Unit Testing, Part 3

By Duncan Smith Dec 16 0

Wrapped Computer

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.

In two previous articles, I wrote about SystemWrapper, a library that makes it easier to test code that depends on .NET system APIs. In this article, I’ll provide more details on how I use SystemWrapper for Time Tortoise testing.

SystemWrapper

To review: One way to make code more testable is to have classes depend on interfaces rather than concrete implementations. That allows unit tests to inject test versions of dependencies, which run faster and have no side effects, while product code can inject concrete implementations of the same dependencies.

One challenge when using dependency injection is that not every class inherits from an interface. For some classes, you only get concrete implementations. This is the case for many .NET Framework classes. SystemWrapper solves this problem by providing a complete set of interfaces, along with implementations of these interfaces that delegate to the underlying .NET types.

SystemWrapperCore

SystemWrapper is based on the classic .NET Framework, while Time Tortoise uses .NET Core. Although .NET Standard 2.0 brings in many classic APIs, it’s not compatible enough that I could just import the SystemWrapper code into a .NET Standard project. Making the necessary changes to all of SystemWrapper would be a lot of work, and I wouldn’t be using most of the interfaces and classes, so I decided instead to convert classes as I needed them. As I use classes in Time Tortoise, I add them to projects in a new solution called SystemWrapperCore.

To fill in some missing unit test coverage in the Time Tortoise DailySummary class, I needed the IFileInfo and IFile interfaces and their corresponding wrappers. For consistency, I decided to adopt IDateTime and its wrapper as well, replacing the one-off DateTime interface and implementation I created earlier this year.

As I convert the SystemWrapper code for use with .NET Standard, I get it to compile by commenting out anything that .NET Standard 2.0 doesn’t support. For example, there’s no .NET Standard version of FileInfo.GetAccessControl, so I commented out that method in the SystemWrapperCore version of IFileInfo and FileInfoWrap. If this method appears in a future .NET Standard release, I can just uncomment it.

In addition to depending on .NET, SystemWrapper interfaces and classes depend on other SystemWrapper interfaces and classes. So another part of the conversion process was to pull in the minimum set of files that were required to get IFileInfo, IFile, and IDateTime to compile. This required a total of twelve interfaces and ten wrapper classes.

Since I haven’t uploaded SystemWrapperCore to nuget.org, I used it in Time Tortoise by compiling its two projects to two DLLs, and referencing those local DLLs.

Using SystemWrapper for Testing

With SystemWrapperCore available, I was able to fill in some unit test gaps involving the class that generates the Daily Summary file. First, I made the following changes to the DailySummary class:

Set Up Dependency Injection

Add IFileInfo, IFile, and IDateTime parameters to the DailySummary constructor. This matches the standard dependency injection pattern that I use elsewhere in Time Tortoise to facilitate unit testing.

Use Interfaces

When declaring variables, replace concrete class types with interface types. For example, the old section of code that created the directory for the daily summary file used the concrete FileInfo class:

var fileInfo = new FileInfo(fullName);
fileInfo.Directory.Create();

In the new code, a _fileInfo variable of type IFileInfo has been initialized in the constructor. So the directory creation process now looks like this:

_fileInfo.Initialize(fullName);
_fileInfo.Directory.Create();

Inject Dependencies

With constructors set up to accept dependencies, callers can now pass appropriate dependencies based on their needs. For example, a unit test that needs to test the file creation process without creating real files can pass a Mock<IFileInfo> to the DailySummary constructor. But the product code needs a real FileInfo to create a real file. However, it can’t pass FileInfo in directly because the standard .NET FileInfo class doesn’t inherit from IFileInfo. Instead, the product code passes FileInfoWrap, the SystemWrapper class that inherits from IFileInfo and delegates its functionality to FileInfo. So when DailySummary.WriteFile is called during a test run, the WriteAllText call that it makes doesn’t do anything other than record that the call happened. But when it’s called while a user is running the app, a real file is written to disk.

(Image credit: cellanr)

Categories: TT

Prev
Next

Stay in the Know

I'm trying out the latest learning techniques on software development concepts, and writing about what works best. Sound interesting? Subscribe to my free newsletter to keep up to date. Learn More
Unsubscribing is easy, and I'll keep your email address private.

Getting Started

Are you new here? Check out my review posts for a tour of the archives:

  • 2023 in Review: 50 LeetCode Tips
  • 2022 in Review: Content Bots
  • 2021 in Review: Thoughts on Solving Programming Puzzles
  • Lessons from the 2020 LeetCode Monthly Challenges
  • 2019 in Review
  • Competitive Programming Frequently Asked Questions: 2018 In Review
  • What I Learned Working On Time Tortoise in 2017
  • 2016 in Review
  • 2015 in Review
  • 2015 Summer Review

Archives

Recent Posts

  • Do Coding Bots Mean the End of Coding Interviews? December 31, 2024
  • Another Project for 2024 May 8, 2024
  • Dynamic Programming Wrap-Up May 1, 2024
  • LeetCode 91: Decode Ways April 24, 2024
  • LeetCode 70: Climbing Stairs April 17, 2024
  • LeetCode 221: Maximal Square April 10, 2024
  • Using Dynamic Programming for Maximum Product Subarray April 3, 2024
  • LeetCode 62: Unique Paths March 27, 2024
  • LeetCode 416: Partition Equal Subset Sum March 20, 2024
  • LeetCode 1143: Longest Common Subsequence March 13, 2024
Red-Green-Code
  • Home
  • About
  • Contact
  • Project 462
  • CP FAQ
  • Newsletter
Copyright © 2025 Duncan Smith