Question

We’re using Xunit for testing. We’re running our tests via the built-in Visual Studio 2013 Test Runner, using the Xunit plugin.

The issue is that some of the tests need to refer to files on the filesystem. It seems that Xunit (or the VS Test Runner—not sure which), copies the assembles, but not any supporting files in the bin directory, to another directory before executing the tests, hence our test files are not found. [The MS Testing framework specifies attributes for listing files to be copied, but Xunit does not.]

How to either disable this copying behaviour, or else programmatically determine the original bin/ directory location to fetch the files?

It seems that most proposed solutions (including on the Xunit bug-tracker site) suggest storing the files as embedded resources instead of ‘copy always’ files. However, this is not always practical, for example: testing file manipulation code, and (in one case) code which wants a Sqlite database file.

Was it helpful?

Solution

Okay, typical, just as soon as I post the question, I find the answer myself…

The gist is that the copying (Shadow Copying) of assemblies seems to be done by the .NET framework, not by Visual Studio or by Xunit.

We had been using Assembly.Location to locate the assembly file, and hence the test files. However, this was wrong, since it gave us the location of the Shadow-Copied assembles instead of the originals.

Instead you should use Assembly.CodeBase to fetch the base assembly code location. However, this is a (File) URL, so it’s necessary to extract the path from the URL. The new (C#) code looks like this:

var codeBaseUrl = new Uri(Assembly.GetExecutingAssembly().CodeBase);
var codeBasePath = Uri.UnescapeDataString(codeBaseUrl.AbsolutePath);
var dirPath = Path.GetDirectoryName(codeBasePath);
return Path.Combine(dirPath, relativePath);

…where relativePath is the path relative to the Bin\ directory.

OTHER TIPS

After a bit of search I found the solution here: https://msdn.microsoft.com/en-us/library/ms182475.aspx.

Particularly, the first step has been enough for me:

If they are specific to one test project, include them as content files in the Visual Studio test project. Select them in Solution Explorer and set the Copy to Output property to Copy if Newer.

associated to the following code:

var filename = "./Resources/fake.pdf";
var stream = File.OpenRead(filename);

The test project (note the Resource folder) Cfg of files in the resource folder

As of .NET5 (perhaps earlier), CodeBase no longer works, so the solution is now to copy all of your files to the bin directory first and use this as your known location.

What makes this OK now, which was always a total pain in the past, is that you can copy a directory to your bin folder easily from your csproj file.

<ItemGroup>
  <None 
     Include="TestFiles\**" 
     CopyToOutputDirectory="PreserveNewest" 
     LinkBase="TestFiles\" />
</ItemGroup>

Where TestFiles is in the root of your project folder. Now you can access these files with the following helper method.

public static class TestUtils
{
    public static string GetTestPath(string relativePath)
    {
        var codeBaseUrl = new Uri(Assembly.GetExecutingAssembly().Location);
        var codeBasePath = Uri.UnescapeDataString(codeBaseUrl.AbsolutePath);
        var dirPath = Path.GetDirectoryName(codeBasePath);
        return Path.Combine(dirPath, "TestFiles", relativePath);
    }
}

I am running .Net Core 1.0 on Mac. Assembly.GetExecutingAssembly is unavailable. I use the following code instead:

var location = typeof(YourClassName).GetTypeInfo().Assembly.Location;
var dirPath = Path.GetDirectoryName(location);
return Path.Combine(dirPath, relativePath);

relativePath is the path relative to the directory of your DLL.

After struggling with trying to identify directories and copy files (there seem to be multiple directories in play and it's hard to pin down the right one), I found that you can just turn off shadow copying; at that point the unit tests reference content in the bin/{configuration} folder again.

To do so, follow the directions here. It seems to be as simple as setting shadowCopy to false:

{
    "shadowCopy": false
}

I'm unclear whether this setting has any adverse interactions with other settings (e.g. appDomain). If anyone knows, your comments would be welcome.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top