More Examples of System.IO.Abstractions

In a previous post, I had discussed System.IO.Abstractions and its use in mocking a file system for unit tests. I've found that in real world use, it's often necessary to flesh out a file system complete with sub-directories, binary files, and more. Below are some examples of these exercises.

Sub-directories

I first tried to create sub-directories with a file by telling the file system to create "C:\MyFolder\MySubFolder\HelloWorld.txt". However, when I used a debugger to step through the code, I saw that the file name was "C:\MyFolder\MySubFolder\HelloWorld.txt" rather than "HelloWorld.txt" and that I couldn't navigate down into a folder and sub-folder named "MyFolder" and "MySubFolder" respectively.

As always, there's a pretty good chance I was doing something wrong -- I'm a big enough developer to admit I do some pretty dumb stuff at times, particularly when I start banging away at the keyboard before reading the documentation.

So, I played around a bit and, -- doh! -- there's nothing magical about System.IO.Abstractions. You work with the TestingHelpers pretty much as you would with the real file system.

Let's take a look at an example below that creates valid directories within a mock file system.

References

The first thing you need to do is add a reference in your project to System.IO.Abstractions and System.IO.Abstractions.TestingHelpers. Your best bet is to use NuGet for this:


Next, add the using statements to your code:

using System.Collections.Generic;
using System.IO.Abstractions;
using System.IO.Abstractions.TestingHelpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using DailyFtp.Handlers.FileProcessHandler.Nipsco;

After that, you're ready to start mocking out your file system. I find it easier to create a method that returns the fully mocked out file system. That way, all that housekeeping code can be tucked away and out of sight once you're done, making the code that uses the mock file system easier to read.

Here's a stripped down version of just such a method:

private static MockFileSystem BuildFileSystem()
{
    MockFileSystem result = new MockFileSystem();
      
    // do some work
    
    return result;
}

And here's how it's used:

IFileSystem fileSystem = BuildFileSystem();

This pleases the inner coder/philosopher in me that insists that unit tests (and code in general) should be as straightforward and simple to read as possible. Push the details down and abstract your ideas up, however you choose to see it.

Once you have the references, a basic method, and how it's called out of the way, you can focus on building the file system to your exact needs.

To add directories and sub-directories to your mock file system, use the Directory.CreateDirectory() method, as shown below:

MockFileSystem result = new MockFileSystem();
var roootDirectory = result.Directory.CreateDirectory(@"C:\");
var dailyFtpDirectory = roootDirectory.CreateSubdirectory("DailyFtp");
var dataDirectory = dailyFtpDirectory.CreateSubdirectory("Data");
var outputDirectory = dailyFtpDirectory.CreateSubdirectory("Output");

This results in a file system with a root directory ("\" on a C: drive), a sub-directory named "DailyFtp" under that, and two sub-directories ("Data" and "Output") under that.

Non-Trivial Text Files

I appreciate all the examples which use a file called "Test.txt" containing the text "This is a test." Those got me off the ground, as far as System.IO.Abstractions goes. But, there was no way I was going to cram my sample XML files into the statement used by most of these examples.

Instead, I chose to create a method that returned the XML, and used that as a parameter for the MockFileSystem.File.WriteAllText() method.

Here's an example of such a method:

private static string FileContents01()
{
    const string xml = @"<?xml version=""1.0""?>
<XML id=""meter"">
    <DATA>
        <Email_Sch/>
        <Email_Alarm/>
        <SRL_NUM>111100409D4E9858</SRL_NUM>
        <METER_ID>ACME MFG PLANT</METER_ID>
        <MAC>00:04:D9:C4:E0:5F</MAC>
        <METER_ADDR>100 Industrial Rd, Big Town, AR</METER_ADDR>
        <METER_TIME>11/18/14 15:00 Tuesday</METER_TIME>
        <IP>127.0.0.191</IP>
        <TSF>1</TSF>
        <MODEL>WEPM</MODEL>
        <VER>112512_WEPM_ACE</VER>
        <SB>
            <TS>11/18/2014 15:00:00</TS>
            <C0>100</C0>
            <C1>0</C1>
            <C2>0</C2>
            <C3>0</C3>
        </SB>

    </DATA>
</XML>
";
    return xml;
}

And here it's being used to add a file to the sub-directory "C:\DailyFtp\Data\":

if (result.Directory.Exists(dataDirectory.FullName))
{
    result.File.WriteAllText(
        @"C:\DailyFtp\Data\FTP_111100409D4E9858_1217141200.xml",
        FileContents01()
    );
}

I don't usually split statements like that across multiple lines but it's easier to read within this post that way.

If I correctly follow the steps for creating sub-directories, then I probably don't need the if...then statement. But, I figure in production-ready code it's a good idea because it provides a place to take action. Or, you could put the .WriteAllText() method(s) within a try... catch block.

Binary Files

Binary files are pretty simple to work with, too:

result.File.WriteAllBytes(
    @"C:\DailyFtp\Data\FTP_111100409D4E9858_1217141200.xml", 
    new byte[] { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 }
);

And, of course, you don't have to use a new byte array; you can send into .WriteAllBytes() any array of bytes, whether from a method (like we did above for the XML file) or from a service or stream. (On second thought, if you're sending in a byte array from a service or external stream, then your test  isn't so self-contained, now is it?)

Conclusion

System.IO.Abstractions does indeed provide a rich and faithful reproduction of System.IO. While it's tempting to rely on a real file system and batch files to set up your tests time and time again), your tests will be more reproducible and reliable if you spend the time & energy to create a self-contained mock file system. Hopefully, the examples above illustrate how easy it is.


Comments

Popular posts from this blog

Using Reference Aliases

List of Visual Studio Keyboard Shortcuts