The following is my way of reading a file from local disk when using C#’s NUnit unit test framework.
I’ve used this method in two small projects now. One was concerned with getting a local directories contents, and the other wanted to load a particular HTML file to use as a fixture for multiple tests. This approach works for both, and can should work for any file type.
OK, let’s dive right into the code.
Reading A File From Local Disk
Here’s our starting point – a C# unit test using NUnit:
using SeoAnalysis;
namespace SeoAnalysisTests;
public class Tests
{
[Test]
public void Test1()
{
var html = LoadFileToString("single-paragraph.html");
Assert.That(new Class1().CountWords(html), Is.EqualTo(2));
}
[Test]
public void Test2()
{
var html = LoadFileToString("multiple-paragraphs.html");
Assert.That(new Class1().CountWords(html), Is.EqualTo(7));
}
private static string LoadFileToString(string fileName)
{
var runningDir = TestContext.CurrentContext.TestDirectory;
var projectDir = Directory.GetParent(runningDir).Parent.FullName;
var fixtureDir = Path.Join(projectDir, "/../Fixtures/Html/");
var htmlFilePath = Path.Join(fixtureDir, fileName);
Console.WriteLine(htmlFilePath);
return File.ReadAllText(
htmlFilePath
);
}
}
Code language: C# (cs)
Forgive the terrible naming of Class1
, this was very much about me examining an idea with code, rather than it being a real project.
Here’s how this looks as a project:
Again, the content isn’t important.
What matter is we have a unit test that needs some string
data to feed into some implementation code, so it can be tested. This could be a CSV, a YAML file, or any content really. In this particular case it’s some basic HTML.
Note that my two fixture files live inside the test project.
The Problem
The problem I faced is that I couldn’t use a relative path from the test file to another file as when the Test project runs, it doesn’t run from the same directory that the code resides in.
On my initial attempt I figured that I had my test file on disk at:
/home/chris/Development/SeoAnalysis/SeoAnalysisTests/UnitTest1.cs
So all I would need to do is this, right?
var htmlFilePath = Path.Join(Environment.CurrentDirectory, "./Fixtures/Html/", fileName);
Code language: JavaScript (javascript)
It seems to make sense. My UnitTest1.cs
file lives in the project root.
From there I have a directory structure of Fixtures/Html
, inside which live the two HTML files I want to access.
But this path doesn’t resolve to where I initially thought:
System.IO.DirectoryNotFoundException : Could not find a part of the path '/home/chris/Development/SeoAnalysis/SeoAnalysisTests/bin/Debug/net7.0/Fixtures/Html/single-paragraph.html'.
Code language: JavaScript (javascript)
Which does make sense in hindsight. The code is being compiled for a specific version of Dot Net that I have configured for my project:
I thought what I needed to do was somehow tell the tests to look at that very specific path:
.../SeoAnalysisTests/bin/Debug/net7.0/Fixtures/Html/single-paragraph.html
But that’s not the case.
Why?
Because the Fixture files are not copied into that directory when the project is compiled:
So the files are not there. They are where I already know they are, which is inside my project directory.
The trick (or fix?) therefore is to tell the tests to look at my Test project directory regardless of the compilation directory / where the actual code runs from.
The Fix
We saw the fix at the top of this page.
But here it is again:
var runningDir = TestContext.CurrentContext.TestDirectory;
var projectDir = Directory.GetParent(runningDir).Parent.FullName;
Code language: C# (cs)
This code uses the TestContext.CurrentContext.TestDirectory
property to get the current running directory of the NUnit test.
The TestDirectory
property provides the directory path where the test assembly is located.
In short we now have the exact location our test code is running from.
Or to put it another way:
var runningDir = "/home/chris/Development/SeoAnalysis/SeoAnalysisTests/bin/Debug/net7.0";
Code language: C# (cs)
Then, the Directory.GetParent
method is used to get the parent directory of the running directory.
Here’s the kicker: we are three directories deep: bin/Debug/net7.0
So we need to go up, up, and up again, to get back to our actual SeoAnalysisTests
root directory.
Console.WriteLine(Directory.GetParent(runningDir).FullName);
// get parent:
// /home/chris/Development/SeoAnalysis/SeoAnalysisTests/bin/Debug
Console.WriteLine(Directory.GetParent(runningDir).Parent.FullName);
// get parent's parent:
// /home/chris/Development/SeoAnalysis/SeoAnalysisTests/bin
Console.WriteLine(Directory.GetParent(runningDir).Parent.Parent.FullName);
// get parent's parent's parent:
// /home/chris/Development/SeoAnalysis/SeoAnalysisTests
Code language: PHP (php)
The FullName
property is used to get the full path of the parent directory. As a side note I get the full path regardless of whether or not I use FullName
, but I used it because the docs indicated I should.
Parent Parent Parent, Or Relative Path?
In the above I have used chained calls to .Parent
to keep going up the directory structure.
Another way is to use relative paths:
var runningDir = TestContext.CurrentContext.TestDirectory;
var projectDir = Directory.GetParent(runningDir).Parent.FullName;
var fixtureDir = Path.Join(projectDir, "/../Fixtures/Html/");
Code language: PHP (php)
Note there the use of /../
which tells the computer to go up a directory.
Looking back at the code now I’m not sure whether I prefer the chained .Parent
calls or having that relative path. I think I prefer the chained calls.
How I Use This In My C# NUnit Project
I guess if you have a really simple project like I do above, it doesn’t matter how you do this. Dump the file loader code into your test file and that’s probably good enough.
But what if you want to load files in multiple tests?
Probably best to move this test fixture file loader code to somewhere you can re-use.
One way may be:
// {solutionRoot}/SeoAnalysisTests/Helper/FixtureFileLoader.cs
namespace SeoAnalysisTests.Helper;
public static class FixtureFileLoader
{
public static string LoadFileToString(string fixtureDir, string fileName)
{
var runningDir = TestContext.CurrentContext.TestDirectory;
var projectDir = Directory.GetParent(runningDir).Parent.Parent.FullName;
var htmlFilePath = Path.Join(projectDir, fixtureDir, fileName);
Console.WriteLine(htmlFilePath);
return File.ReadAllText(
htmlFilePath
);
}
}
Code language: C# (cs)
Which I could then call:
using SeoAnalysis;
using SeoAnalysisTests.Helper;
namespace SeoAnalysisTests;
public class Tests
{
[Test]
public void Test1()
{
var html = FixtureFileLoader.LoadFileToString("Fixtures/Html", "single-paragraph.html");
Assert.That(new Class1().CountWords(html), Is.EqualTo(2));
}
[Test]
public void Test2()
{
var html = FixtureFileLoader.LoadFileToString("Fixtures/Html", "multiple-paragraphs.html");
Assert.That(new Class1().CountWords(html), Is.EqualTo(7));
}
}
Code language: C# (cs)
It then shouldn’t matter where my tests live, because the FixtureFileLoader
class is in a constant place, so the chained Parent.Parent
calls always go relative to that class.
Getting Directory Contents
I mentioned at the start that this approach is good for loading file contents. And that’s the demo we have seen in the code samples.
But implicit to this is that we can also get the directory contents, if needed.
It’s basically the exact same code, only we don’t look for a specific file:
var runningDir = TestContext.CurrentContext.TestDirectory;
var projectDir = Directory.GetParent(runningDir).Parent.Parent.FullName;
var fixtureDir = Path.Join(projectDir, "/Fixtures/FilesWithNumericPrefix");
Code language: C# (cs)
Here we end up with a fixtureDir
(or fixture directory). It’s a path to a directory, not a path to a specific file.
You might use it like this:
var dirContents = Directory.EnumerateFiles(fixtureDir);
Code language: C# (cs)
It’s entirely specific to your needs. But that also works well using this method.
Anyway, that’s how I read files in NUnit. Is there a better way? Do let me know by leaving a comment.