Unit Testing File I/O

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 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.

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.

Testing file i/o with Perl - frameworks

#! /usr/bin/perl
use warnings;
use strict;

{
package TestMe;
use File::Path qw{ mkpath };
sub testme {
mkpath('dir', 0, 0777);
}
}

use Test::More;
use Sub::Override;

{
my $override = 'Sub::Override'->new('TestMe::mkpath' => sub {
my ($dir, $verbose, $mode) = @_;
is $dir, 'dir', 'right dir';
is $verbose, 0, 'no verbosity';
is $mode, 0777, 'correct mode';
});
TestMe::testme();
} # End of scope, $overrides si destroyed, original mkpath is restored.

Note that you override mkpath in the TestMe package, as it's been imported by the use clause.

python - Is it a bad practice to do I/O in unitest

But some people told me it's not a good practice to do I/O in unitest, is it true?

I don't think it's necessarily bad to perform I/O in a unit test.

The only caveat is that if your unit test relies on some pre-existing data files in order to do its thing, then the files should be considered part of the unit test (and therefore version controlled etc).

P.S. Have you tried asking those who gave you this advice for specific reasons behind the recommendation?



Related Topics



Leave a reply



Submit