Testing file stream writing

I’ve been adding unit test coverage to a project which uses a streaming approach to write potentially big files. With such projects it’s tempting to rely on integration tests for the convenience of not having to mock out the file system. I prefer the speed and control of unit tests which use a mocking framework like Moq combined with the System.IO.Abstractions library to mock out the file system calls.

I ran into a problem when trying to test what had been written to a file stream and developed a trick that I’ve not seen documented anywhere else.

Testing file streams

To open a file for reading or writing one can use the File.Open method to create a stream and the StreamWriter class to write to this stream.

// fs is an IFileSystem from System.IO.Abstractions
using(var fileStream = fs.File.Open("some-file-path.txt", 
    FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
using(var writer = new StreamWriter(fileStream))
{
    writer.Write("something");
}

To test this we can setup a mock to pass in a stream to capture the information written to the file. We can use the .Net MemoryStream class to store the information and the StreamReader to read the result.

var mockFileSystem = new Mock<IFileSystem>();
var fs = mockFileSystem.Object;
var memoryStream = new MemoryStream();
mockFileSystem.Setup(f => f.File.Open("some-file-path.txt", It.IsAny<FileMode>(), 
        It.IsAny<FileAccess>(), It.IsAny<FileShare>()))
    .Returns(memoryStream);

// Write to file
using(var fileStream = fs.File.Open("some-file-path.txt", FileMode.OpenOrCreate, 
        FileAccess.ReadWrite, FileShare.None))
using(var writer = new StreamWriter(fileStream))
{
    writer.Write("something");
}
var result = new StreamReader(stream).ReadToEnd();

Assert.That(result, Is.EqualTo("something"));

The problem

Running this code, we get the error System.ArgumentException: 'Stream was not readable.'. The problem is that the memoryStream is being disposed of at the end of the using statement which is before we can read from it.

To solve this problem I created a class called CopyingMemoryStream which derives from the MemoryStream class. This class then overrides the Write method and copies the bytes being written into a StringBuilder using a byte[] to char[] conversion function.

public class CopyingMemoryStream : MemoryStream
{
    private readonly Func<byte[], char[]> _convertToChars;
    private readonly StringBuilder _sb = new StringBuilder();
    public CopyingMemoryStream(Func<byte[], char[]> convertToChars = null)
    {
        _convertToChars = convertToChars ?? (b => Encoding.UTF8.GetChars(b));
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        _sb.Append(_convertToChars(buffer.Take(count).ToArray()));
        base.Write(buffer, offset, count);
    }

    public string Written => _sb.ToString();
}

With this class we can now assert against the Written property and the test passes:

var mockFileSystem = new Mock<IFileSystem>();
var fs = mockFileSystem.Object;
var stream = new CopyingMemoryStream();
mockFileSystem.Setup(f => f.File.Open("some-file-path.txt", 
        It.IsAny<FileMode>(), It.IsAny<FileAccess>(), It.IsAny<FileShare>()))
    .Returns(stream);

// Write to file
using (var fileStream = fs.File.Open("some-file-path.txt", 
        FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
using (var writer = new StreamWriter(fileStream))
{
    writer.Write("something");
}
var result = stream.Written;

Assert.That(result, Is.EqualTo("something"));

I think this is a nice solution to the problem described and it has helped me in my testing.

Written on June 11, 2017
Any comments/questions/suggestions? I'd love to hear them!