the following is the original text of the book 《The Art of Unit Testing, Second Edition》: Extract an interface to allow replacing underlying implementation
Listing 3.1 Extracting a class that touches the filesystem and calling it
public bool IsValidLogFileName(string fileName)
{
FileExtensionManager mgr =
new FileExtensionManager();
return mgr.IsValid(fileName);
}
class FileExtensionManager
{
public bool IsValid(string fileName)
{
//read some file here
}
}
Listing 3.2 Extracting an interface from a known class
public class FileExtensionManager : IExtensionManager
{
public bool IsValid(string fileName)
{
//read some file here
}
}
public interface IExtensionManager
{
bool IsValid (string fileName);
}
//the unit of work under test:
public bool IsValidLogFileName(string fileName)
{
IExtensionManager mgr =
new FileExtensionManager();
return mgr.IsValid(fileName);
}
Note that 'IExtensionManager mgr = new FileExtensionManager();' in the 'IsValidLogFileName' method is causing issues later on.
Listing 3.4 Injecting your stub using constructor injection
public class LogAnalyzer
{
private IExtensionManager manager;
public LogAnalyzer(IExtensionManager mgr)
{
manager = mgr;
}
public bool IsValidLogFileName(string fileName)
{
return manager.IsValid(fileName); //The tested code has been disrupted; 'IExtensionManager mgr = new FileExtensionManager();' has been deleted!!!!!
}
}
public interface IExtensionManager
{
bool IsValid(string fileName);
}
[TestFixture]
public class LogAnalyzerTests
{
[Test]
public void
IsValidFileName_NameSupportedExtension_ReturnsTrue()
{
FakeExtensionManager myFakeManager =
new FakeExtensionManager();
myFakeManager.WillBeValid = true;
LogAnalyzer log =
new LogAnalyzer (myFakeManager);
bool result = log.IsValidLogFileName("short.ext");
Assert.True(result);
}
}
internal class FakeExtensionManager : IExtensionManager
{
public bool WillBeValid = false;
public bool IsValid(string fileName)
{
return WillBeValid;
}
}
Please note the IsValidLogFileName method above. Although the modifications allow the test code to run correctly, but the tested code has been disrupted; 'IExtensionManager mgr = new FileExtensionManager();' has been deleted.
This disrupts the integrity of the source code functionality. When we run the source code normally, the program will not be able to call the IsValid method of the FileExtensionManager class correctly! You will not get the same result as shown in Listing 3.2. If I want to restore the source code, where should I place 'IExtensionManager mgr = new FileExtensionManager();' in line 3.4?So that the code calls the IsValid method of the class FileExtensionManager during runtime and the IsValid method of the class FakeExtensionManager during testing
Whatever you're using to create a new instance of the LogAnalyzer
should also create the appropriate instance of a class that implements IExtensionManager
and inject it via the constructor.
Just like your tests are instantiating and injecting a fake, your real code should instantiate and inject a real instance. This could be using an IoC container or 'Pure DI'.
To answer the question:
...where should I place 'IExtensionManager mgr = new FileExtensionManager()?
You do this in your 'Composition Root'.