In Chapter 3 of his book The Art of Unit Testing: with Examples in C#, Roy Osherove illustrates the issue of undesirable external dependencies in code under test.
He shows this with a method named IsValidLogFileName which takes in a filename string as an argument, reads in a config file from the local filesystem and checks whether the given filename extension exists in the config file. Here's what such a class would look like:
public class LogAnalyzer { public bool IsValidLogFileName(string fileName) { string [] extensions = System.IO.File.ReadAllLines(@"C:\Users\Public\ext.config"); foreach (string ext in extensions) { if (fileName.EndsWith(ext)) { return true; } } return false; } } The issue here is that testing IsValidLogFileName has a dependency on the local filesystem.
The author introduces the concept of stubs as a means of removing this dependency. In particular, he describes the following design:
Create an interface:
interface IExtensionManager { bool IsValid(string fileName); } an implementing production class:
class FileExtensionManager : IExtensionManager { bool IsValid(string fileName) { string [] extensions = System.IO.File.ReadAllLines(@"C:\Users\Public\ext.config"); foreach (string ext in extensions) { if (fileName.EndsWith(ext)) { return true; } } return false; } } a stub:
class FakeExtensionManager : IExtensionManager { bool IsValid(string fileName) { string [] extensions = { "txt", "c", "cpp", "cs"}; foreach (string ext in extensions) { if (fileName.EndsWith(ext)) { return true; } } return false; } } refactor LogAnalyzer as follows:
public class LogAnalyzer { IExtensionManager em; public LogAnalyzer(IExtensionManager extensionManager) { em = extensionManager; } public bool IsValidLogFileName(string fileName) { return em.IsValid(fileName); } } run production code like this:
LogAnalyzer la = new LogAnalyzer(new FileExtensionManager);
and test code like this:
LogAnalyzer la = new LogAnalyzer(new FakeExtensionManager);
The part I don't understand is why we have to extract the validation logic from the original IsValidLogFileName by making an ExtensionManager interface. Wouldn't it make more sense to create something like a ConfigManager interface and refactor as follows:
interface:
interface IConfigManager { string [] GetFileExtensionConfig(); } an implementing production class:
class LocalConfigManager : IConfigManager { string [] GetFileExtensionConfig( ) { return System.IO.File.ReadAllLines(@"C:\Users\Public\ext.config"); } } a stub:
class FakeConfigManager : IConfigManager { string [] GetFileExtensionConfig( ) { string [] extensions = { "txt", "c", "cpp", "cs"}; return extensions; } } refactor LogAnalyzer as follows:
public class LogAnalyzer { IConfigManager cm; public LogAnalyzer(IConfigManager configManager) { cm = configManager; } public bool IsValidLogFileName(string fileName) { string [] extensions = cm.GetFileExtensionConfig( ); foreach (string ext in extensions) { if (fileName.EndsWith(ext)) { return true; } } return false; } run production code like this:
LogAnalyzer la = new LogAnalyzer(new LocalConfigManager);
and test code like this:
LogAnalyzer la = new LogAnalyzer(new FakeConfigManager);