How to mock static class or static member for testing
One of the problems with static members or static classes is that you can’t mock them for proper unit-testing. Instead of taking this fact for granted, let’s demonstrate it. Assume that we have the following classes (please note, this is just an example, not production code or anything that I’ll be proud of later on):
public class CsvDataExtractor
{
private string _documentPath;
public CsvDataExtractor(string documentPath)
{
_documentPath = document;
}
public string ExtractFullName()
{
string content = CsvDocumentReader.GetContent(_documentPath);
string fullName = // extract full name logic
return fullName;
}
}
[Test]
public void ExtractFullName_DocumentHasFirstNameAndLastNameOnly()
{
CsvDataExtractor extractor = new CsvDataExtractor(“c:\test.csv”);
string result = extractor.ExtractFullName();
Assert.AreEqual(“ellenbogen”, result);
}
Here are our options to solve this dependency so we could test ExtractFullName method by itself:
Now CsvDataExtrator can get IDocumentReader in its constructor and use it in ExtractFullName. We could mock the interface and determine the result we want to get from the mocked object. Here is the refactored version:
public interface IDocumentReader
{
string GetContent(string documentPath);
}
public class CsvDataExtractor
{
private string _documentPath;
private IDocumentReader _reader;
public CsvDataExtractor(IDocumentReader reader, string documentPath)
{
_documentPath = document;
_reader = reader;
}
public string ExtractFullName()
{
string content = _reader.GetContent(_documentPath);
string fullName = // extract full name logic
return fullName;
}
}
[Test]
public void ExtractFullName_DocumentHasFirstNameAndLastNameOnly()
{
DocumentReaderStub reader = new DocumentReaderStub();
reader.ContentToReturn = “oren,ellenbogen”;
CsvDataExtractor extractor = new CsvDataExtractor(reader, “not important document path”);
string result = extractor.ExtractFullName();
Assert.AreEqual(“ellenbogen”, result);
}
internal class DocumentReaderStub : IDocumentReader
{
public string ContentToReturn;
public string GetContent(string documentPath) { return ContentToReturn; }
}
Cons: (1) It’s not always possible to refactor the original static class. For example, we can’t change Microsoft’s Guid static class to control NewGuid() method, assuming that we need to mock it.
We can create an interface that expose all the functionality of the static class. Then all we need to do is wrap it with a simple class that will delegate the calls to the static class:
// the interface should look exactly like the static class we want to wrap !
public interface IDocumentReader
{
string GetContent(string documentPath);
}
// This class will simply delegate calls to the static class
public class CsvDocumentReaderWrapper : IDocumentReader
{
public string GetContent(string documentPath)
{
return CsvDocumentReader.GetContent(documentPath); // call the original static class
}
}
The CsvDataExtractor implementation and the tests are exactly the same as option 1.
public class CsvDataExtractor
{
private string _documentPath;
public CsvDataExtractor(string documentPath)
{
_documentPath = document;
}
public string ExtractFullName()
{
string content = GetDocumentContent();
string fullName = // extract full name logic
return fullName;
}
protected virtual string GetDocumentContent()
{
return CsvDocumentReader.GetContent(_documentPath);
}
}
[Test]
public void ExtractFullName_DocumentHasFirstNameAndLastNameOnly()
{
TestableCsvDataExtractor extractor = new TestableCsvDataExtractor(“not important document path”);
extractor.ContentToReturn = “oren,ellenbogen”;
string result = extractor.ExtractFullName();
Assert.AreEqual(“ellenbogen”, result);
}
public class TestableCsvDataExtractor : CsvDataExtractor
{
public string ContentToReturn;
public TestableCsvDataExtractor(string documentPath) : base(documentPath)
{
}
protected virtual string GetDocumentContent()
{
return ContentToReturn;
}
}