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.
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.
Given that you control the creation of the stream and thatHow to assert that
encoder.Save(outStream)
is called?
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
How to Use C# Style Enumerations in Ruby
Reading Files in a Zip Archive, Without Unzipping The Archive
Get Route for Base Class of Sti Class in Rails
How to Share Code Across Chef Cookbooks in a Chef-Repo
Why Are Global Methods Allowed to Be Defined Outside of a Class in Ruby
How to Wait for System Command to End
Actionmailer Pass Local Variables to The Erb Template
Why Can't The Mail Block See My Variable
How to Add Usr/Local/Bin to Path Environment Variable on Ubuntu 12.0.4
What Is The Ruby Equivalent to This Curl Request
Escaping Strings for Ruby Sqlite Insert
Deleting a Specific Line in a Text File
Belongs_To Presence in Rails 5 Not Working
Thor Executable - Ignore Task Name
How to Resize Image Only If Width Exceeds with Graphics/Image Magick
Git: Forcing Tests Before Pushing to Local or Remote Master