BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Testing
.NET 3.5+

Mocking Static and Sealed Types Using Wrappers

A key concept in unit testing is isolating the code under test from its dependencies. However, when those dependencies are static or sealed types, creating appropriate test doubles, such as stubs or mocks, becomes more difficult.

Creating the Class Under Test

We can now start to create the class that counts the number of lines in a text file. We'll make the class read the file using ReadAllLines and return the length of the array. If the file does not exist, we'll return -1 to indicate a failure.

If we were not using a wrapper, we might create the class as follows:

public class FileLineCounter
{
    public int GetLinesInFile(string path)
    {
        try
        {
            string[] lines = File.ReadAllLines(path);
            return lines.Length;
        }
        catch (FileNotFoundException)
        {
            return -1;
        }
    }
}

The above class's method would be called as shown below:

FileLineCounter counter = new FileLineCounter();
int lines = counter.GetLinesInFile(@"d:\Test.txt");

The problem with this approach is the impact upon testability. We can't exercise the code without accessing the file system. This would potentially lead to slow tests, which would be considered integration tests, rather than unit tests.

Updating the Class Under Test

To make the FileLineCounter class more suited to unit testing we need to use our FileWrapper class to open the file. To do so we might update the code as shown below. The key change is that an object that implements IFileWrapper is supplied using constructor injection. This is stored and used when we wish to read a file. It means that we can use a FileWrapper instance at run-time and a mocked IFileWrapper for testing.

public class FileLineCounter
{
    IFileWrapper _fileWrapper;

    public FileLineCounter(IFileWrapper fileWrapper)
    {
        _fileWrapper = fileWrapper;
    }

    public int GetLinesInFile(string path)
    {
        try
        {
            string[] lines = _fileWrapper.ReadAllLines(path);
            return lines.Length;
        }
        catch (FileNotFoundException)
        {
            return -1;
        }
    }
}

At run-time, you might call the code as follows:

IFileWrapper wrapper = new FileWrapper();
FileLineCounter counter = new FileLineCounter(wrapper);
int lines = counter.GetLinesInFile(@"d:\Test.txt");

Creating and Using a Mocked Wrapper

With the FileLinesCounter type decoupled from the File class, we can now write a couple of unit tests. The sample code assumes the use of NUnit for testing and Moq for the creation of test doubles.

The first test checks that the counter can correctly determine the number of lines in a text file. The mocked wrapper class is configured to return an array of three strings. We should therefore expect that the GetLinesInFile method will return 3. This is tested with the equality assertion.

The second test covers the possibility that the named file does not exist. To test this we configure the mock object to throw a FileNotFoundException when called with a specific parameter. The assertion checks that the returned value is -1, as expected.

[TestFixture]
public class FileLineCounterTests
{
    Mock<IFileWrapper> _mockFileWrapper;

    [SetUp]
    public void SetUp()
    {
        InitialiseMock();
    }

    [Test]
    public void CountingLinesForAFileReturnsTheCorrectLineCount()
    {
        FileLineCounter counter = new FileLineCounter(_mockFileWrapper.Object);

        int lines = counter.GetLinesInFile(@"d:\Exists.txt");

        Assert.AreEqual(3, lines);
    }

    [Test]
    public void CountingLinesForAMissingFileReturnsMinusOne()
    {
        FileLineCounter counter = new FileLineCounter(_mockFileWrapper.Object);

        int lines = counter.GetLinesInFile(@"d:\Missing.txt");

        Assert.AreEqual(-1, lines);
    }

    private void InitialiseMock()
    {
        string[] lines = new string[] { "Line 1", "Line 2", "Line 3" };

        _mockFileWrapper = new Mock<IFileWrapper>();
        _mockFileWrapper.Setup(m => m.ReadAllLines(@"d:\Exists.txt"))
        .Returns(lines);
        _mockFileWrapper.Setup(m => m.ReadAllLines(@"d:\Missing.txt"))
        .Throws(new FileNotFoundException());
    }
}

Running the two tests shows that the FileLineCounter class is behaving as expected without the need to read a file from disk. This ensures fast-running unit tests and removes any additional complexity that would be required if you decided to use the File class directly and create sample text files for integration testing purposes.

10 September 2013