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
Replace Part of a JSON with Other (Using a String Token)
How to Clear Event Subscriptions in C#
How to Unit Test with Ilogger in ASP.NET Core
Creating Powerpoint Presentations Programmatically
Methodinfo.Invoke with Out Parameter
Retrieve the Respective Coordinates of All Words on the Page with Itextsharp
Visual Studio 2013 Doesn't Discover Unit Tests
The Name '...' Does Not Exist in the Current Context
Entity Framework Core Using Multiple Dbcontexts
How to Ignore a Certificate Error with C# 2.0 Webclient - Without the Certificate
Entity Framework - Invalid Column Name '*_Id"
Asynchronously Sending Emails in C#
How to Create a New Language for Use in Visual Studio