Unit Testing File I/O Methods

Unit Testing File I/O

Check out Tutorial to TDD using Rhino Mocks and SystemWrapper.

SystemWrapper wraps many of System.IO classes including File, FileInfo, Directory, DirectoryInfo, ... . You can see the complete list.

In this tutorial I'm showing how to do testing with MbUnit but it's exactly the same for NUnit.

Your test is going to look something like this:

[Test]
public void When_try_to_create_directory_that_already_exists_return_false()
{
var directoryInfoStub = MockRepository.GenerateStub<IDirectoryInfoWrap>();
directoryInfoStub.Stub(x => x.Exists).Return(true);
Assert.AreEqual(false, new DirectoryInfoSample().TryToCreateDirectory(directoryInfoStub));

directoryInfoStub.AssertWasNotCalled(x => x.Create());
}

Unit Testing File I/O Methods

Check out this How do I unit-test saving file to the disk?

Basically the idea is the same, the FileSystem is a dependency for your class. So introduce a Role/interface that can be mocked in your unit-tests (such that you have no dependency while unit-testing); the methods in the role should be all the stuff you need from the FileSystem -

Read()  # returns file_contents as a string or string[]
Write(file_contents) # same as above

Now you can use a mock to return a canned string array - your class can then process this array. In production code, the real implementation of the role would hit the filesystem to return the array of lines.

How to test methods with I/O that require sample files?

As you describe, what you want to test is that the content of the files is properly validated. File content is just data, and for the validation procedure it should not make a difference where the data actually came from. That means, the "file content" could also be provided as data that in fact is provided directly from the test code:

  • In the simplest case the data could just be constant strings or byte arrays.
  • If it gets a bit more complex you could have some test helper methods that create the data.
  • You might even consider using the actual component that is responsible for creating the data if that does not violate any of your testing goals.

This requires that you are able to bypass the reading of the file content in some way, for example by mocking the file access operations. Or, by separating the reading of the file from the validation of the content such that the validation part already gets some in-memory data to validate and can be tested separately.

Whether your scenario allows to go this way is not clear from your question. If it is possible, however, it has some nice properties: Your tests are easier to maintain, because you are not dealing with a number of files but have everything in one place (namely the file with the tests). You are less dependent on file system issues like lack of write or read access, case sensitivity of file names and the like.

Unit testing with I/O dependencies

This is all a matter of design. Try to avoid tight coupling to implementation concerns (classes should depend on abstractions and not on concretions).

Consider the following based on your current design

public interface IBitmapService {
void SaveBitmapAsPngImage(Uri path, BitmapSource renderBitmap);
}

public interface IFileSystem {
Stream OpenOrCreateFileStream(string path);
}

public class PhysicalFileSystem : IFileSystem {
public Stream OpenOrCreateFileStream(string path) {
return new FileStream(path, FileMode.OpenOrCreate);
}
}

public class BitmapService : IBitmapService {
private readonly IFileSystem fileSystem;

public BitmapService(IFileSystem fileSystem) {
this.fileSystem = fileSystem;
}

// SaveBitmapAsPngImage(path, renderBitmap);
public void SaveBitmapAsPngImage(Uri path, BitmapSource renderBitmap) {
// Create a file stream for saving image
using (var outStream = fileSystem.OpenOrCreateFileStream(path.LocalPath)) {
// Use png encoder for our data
PngBitmapEncoder encoder = new PngBitmapEncoder();
// push the rendered bitmap to it
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
// save the data to the stream
encoder.Save(outStream);
}
}
}

public interface IImageDrawingCombiner {
void CombineDrawingsIntoImage(Uri path, Canvas surface);
}

public class ImageDrawingCombiner : IImageDrawingCombiner {
private readonly IBitmapService service;

public ImageDrawingCombiner(IBitmapService service) {
this.service = service;
}

/// <summary>
/// Save image to a specified location in path
/// </summary>
/// <param name="path">Location to save the image</param>
/// <param name="surface">The image as canvas</param>
public void CombineDrawingsIntoImage(Uri path, Canvas surface) {
var size = new Size(surface.ActualWidth, surface.ActualHeight);
// Create a render bitmap and push the surface to it
var renderBitmap = new RenderTargetBitmap(
(int)size.Width, (int)size.Height, 96d, 96d, PixelFormats.Pbgra32);
renderBitmap.Render(surface);
service.SaveBitmapAsPngImage(path, renderBitmap);
}
}

FileStream is an implementation concern that can be abstracted out when unit testing in isolation.

Every implementation above can be tested on its own in isolation with their dependencies capable of being mocked and injected as needed. In production, dependencies can be be added in the composition root with a DI container.

How to assert that encoder.Save(outStream) is called?

Given that you control the creation of the stream and that System.IO.Stream is abstract you can easily mock it and verify that it was written to as the encode.Save would have to write to the stream while performing its functions.

Here is a simple example using Moq mocking framework targeting the refactored code in the previous example.

[TestClass]
public class BitmapServiceTest {
[TestMethod]
public void BitmapService_Should_SaveBitmapAsPngImage() {
//Arrange
var mockedStream = Mock.Of<Stream>(_ => _.CanRead == true && _.CanWrite == true);
Mock.Get(mockedStream).SetupAllProperties();
var fileSystemMock = new Mock<IFileSystem>();
fileSystemMock
.Setup(_ => _.OpenOrCreateFileStream(It.IsAny<string>()))
.Returns(mockedStream);

var sut = new BitmapService(fileSystemMock.Object);
var renderBitmap = new Canvas();
var path = new Uri("//A_valid_path");

//Act
sut.SaveBitmapAsPngImage(path, renderBitmap);

//Assert
Mock.Get(mockedStream).Verify(_ => _.Write(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()));
}
}

A commentor suggested using a memory stream, which I would suggest in most other scenarios, but in this case the stream is being disposed within the method under test as it is wrapped in a using statement. This would make calling members on the stream after being disposed to throw exceptions. By mocking the stream outright you have more control of asserting what was called.

How do I test test these file i/o methods using Mock()? Using groovy & spock

The problem with mocking in so many cases is that methods create their own dependencies instead of having them injected or calling a mockable service method creating them. I suggest you refactor your code just a little bit, extracting BufferedReader creation into a service method:

package de.scrum_master.stackoverflow.q56772468

import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType

import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path

class CdmFile {
String readFileContents(Path filePath) {
StringBuilder fileContents = new StringBuilder()
BufferedReader br = createBufferedReader(filePath)
String line
while ((line = br.readLine()) != null) {
fileContents.append(line).append('\n')
}
fileContents
}

void eachLineInFileAsString(
Path filePath,
@ClosureParams(value = SimpleType, options = ['java.lang.String']) Closure applyLine
) {
BufferedReader br = createBufferedReader(filePath)
String line
while ((line = br.readLine()) != null) {
applyLine.call(line)
}
}

protected BufferedReader createBufferedReader(Path filePath) {
Files.newBufferedReader(filePath, StandardCharsets.UTF_8)
}
}

Now mocking is quite simple and you don't even need your test resource file anymore (only if you want to do an integration test without mocks):

package de.scrum_master.stackoverflow.q56772468

import spock.lang.Specification

import java.nio.charset.StandardCharsets
import java.nio.file.NoSuchFileException
import java.nio.file.Path
import java.nio.file.Paths

class CmdFileTest extends Specification {
private static final String filePath = 'mock/cdmTestFile.txt'
private static final String fileContent = """
I heard, that you're settled down
That you found a girl and you're, married now
I heard, that your dreams came true
I guess she gave you things
I didn't give to you
""".stripIndent()

private CdmFile cdmFile

void setup() {
cdmFile = Spy() {
createBufferedReader(Paths.get(filePath)) >> {
new BufferedReader(
new InputStreamReader(
new ByteArrayInputStream(
fileContent.getBytes(StandardCharsets.UTF_8)
)
)
)
}
}
}

def "non-existent file leads to exception"() {
given:
Path notRealPath = Paths.get('notreal.txt')

when:
cdmFile.readFileContents(notRealPath)

then:
thrown NoSuchFileException
}

def "read file contents into a string"() {
given:
Path testFilePath = Paths.get(filePath)

when:
String fileContents = cdmFile.readFileContents(testFilePath)

then:
fileContents.contains("your dreams came true\nI guess")
}

def "handle file content line by line"() {
given:
def result = []
def closure = { line -> result << line }
Path testFilePath = Paths.get(filePath)

when:
cdmFile.eachLineInFileAsString(testFilePath, closure)

then:
result == fileContent.split("\n")
}
}

Please note that I am using a Spy() here, i.e. leaving the original CdmFile object intact and just stubbing the service method createBufferedReader(..) when called with exactly parameter Paths.get(filePath). For other paths the original method is called, which is important for the non-existent file test or if you want to add tests involving real resource file loading like in your own example.

Whenever it is difficult to test a class or component, difficult to inject mocks or otherwise isolate the subject under test, that is a reason to refactor your application code for better testability. When done right also it should also result in better separation of concerns and better componentisation. If your tests become very sophisticated, contrived, brittle and hard to understand and maintain, that is usually a smell and you ought to refactor the application code instead.

How-to do unit-testing of methods involving file input output?

For unit-testing THIS function, you should use stubs for each of the called functions.

Each called function then has its own unit test suite, which exercises that function.

For read_entire_file_to_buffer(), you want at least one test file that overflows the buffer, massively, to verify that you do not crash and burn when they feed you the New York Stock Exchange histories instead of the 40-character config file you were expecting.



Related Topics



Leave a reply



Submit