Unit tests are mandatory for any business logic. But what if your business logic is parsing and processing files? Then you will need files as input in your tests.
At first, I thought about making an extra wrapper around reading the file and mocking the wrapper when parsing it. However, sometimes this is more complicated than necessary. Plus, using actual files has more benefits. For example, if in Production you have a file that gives unexpected results, you can use that exact file in a new unit test, and then fix/change the behavior using Test Driven Development. Or when you use a third-party tool to read the data, and mocking that third-party tool is not feasible.
So, here is a simple example. Say we process our files with the following code:
using System;
using System.IO;
namespace ProcessFile
{
public class ProcessFile
{
public ProcessResult Process(string fileName)
{
if (!File.Exists(fileName))
throw new ArgumentException($"File {fileName} does not exist", nameof(fileName));
// Process file here...
return new ProcessResult();
}
public ProcessResult Process(Stream fileStream)
{
if (!fileStream.CanRead)
throw new ArgumentException("Stream cannot be read", nameof(fileStream));
// Process stream here...
return new ProcessResult();
}
}
}
There are two functions here that need to be covered: one function that takes a file location, and one function that takes a stream. Both very reasonable examples. The actual business logic is left out because it is not relevant for now.
Physical file
The first test looks like this:
[Test]
public void GivenFile_WhenProcess_ThenResult()
{
// Given
var target = new ProcessFile.ProcessFile();
var testLocation = Assembly.GetExecutingAssembly().Location;
var fileLocation = Path.Combine(Path.GetDirectoryName(testLocation), "TestFiles/TestFile.csv");
// When
var result = target.Process(fileLocation);
// Then
Assert.Pass();
}
The actual file is added to the test project. Make sure to set the property CopyToOutputDirectory to either Always or PreserveNewest in the Project file:
<ItemGroup>
<None Include="TestFiles/TestFile.csv" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
The line Assembly.GetExecutingAssembly().Location will give the location of the test assembly dll, which is then used to get the full location to the file we want to test.
Stream
The test for the stream looks like this:
[Test]
public void GivenStream_WhenProcess_ThenResult()
{
// Given
var target = new ProcessFile.ProcessFile();
var assembly = Assembly.GetExecutingAssembly();
var stream = assembly.GetManifestResourceStream("ProcessFileTests.TestFiles.TestFileEmbedded.csv");
// When
var result = target.Process(stream);
// Then
Assert.Pass();
}
Again, the actual file is added to the project, but as an embedded resource. In the project file:
<ItemGroup>
<EmbeddedResource Include="TestFiles/TestFileEmbedded.csv" CopyToOutputDirectory="None" />
</ItemGroup>
Using the testing assembly again, we retrieve the resource using its identifier: ProcessFileTests.TestFiles.TestFileEmbedded.csv. This identifier is created automatically when building: it uses the project namespace plus the relative path, where the directories are notated with a . instead of a /
Summary
So, there you have it: two ways to use files as input for unit testing: one with physical files and one with streams. Files are enclosed in the test project and delivered directly to the code to test.
I put all of this code on GitHub by the way: https://github.com/daanwissing/blog-examples/tree/master/unit-test-files. My intention is to do this with all future blogs, and maybe with previous blogs as well.