In this lesson, you will learn how to break the "hard-coded" coupling of static dependencies to make your legacy code testable through abstraction.
The Problem with Static
When your code directly calls a static method—such as DateTime.Now, File.WriteAllText, or a custom static utility class—it creates a hidden dependency. Because these calls are baked directly into your logic, you cannot intercept or swap them during a unit test. If a method relies on System.IO.File.Delete, your unit test will attempt to modify the actual file system, making it slow, flaky, and non-deterministic.
Refactoring to Abstractions
To make this code testable, we must move from implementation to behavior. We do this by wrapping the static call inside an interface and injecting that interface into our class.
Consider this legacy code that is impossible to unit test without touching the disk:
public class ReportGenerator {
public void Save(string path, string data) {
// Direct static dependency: untestable
File.WriteAllText(path, data);
}
}
To refactor this, we define an abstraction:
public interface IFileSystem {
void Write(string path, string contents);
}
public class FileSystemWrapper : IFileSystem {
public void Write(string path, string contents) => File.WriteAllText(path, contents);
}
Now, update your ReportGenerator to use dependency injection:
public class ReportGenerator {
private readonly IFileSystem _fileSystem;
public ReportGenerator(IFileSystem fileSystem) => _fileSystem = fileSystem;
public void Save(string path, string data) => _fileSystem.Write(path, data);
}
Why This Matters
By introducing IFileSystem, the ReportGenerator no longer cares how the file is saved. In your production code, you pass in the FileSystemWrapper. In your unit tests, you can now use a mocking library (like Moq or NSubstitute) to provide a mock implementation of IFileSystem. This allows you to verify that Write was called with the expected arguments without ever interacting with the actual operating system, effectively isolating your logic from the environment.
Key Takeaway: Never call a static dependency directly; wrap it in an interface to transform an unchangeable static call into a swappable, testable abstraction.