How to Make Sure Your Test Resources Are Always Available
Tags: csharp, testing, dotnet, tutorial
Published: 2023-10-11 12:00:00

In last week's [newsletter](https://webdeveloperdiary.substack.com/p/simplify-tests-by-storing-data-in-resource-files), we explored a tip for keeping tests with complex comparison data tidy. By moving larger expected values into separate resource files, we no longer need to express them in code; they can instead be read from disk during the test. In addition to making tests easier to navigate, the data becomes more readable and less prone to accidental formatting errors – a win-win scenario.

Unfortunately, this doesn’t always work as expected. There are cases where resource files might not be found at the expected path, even when set to be copied to the output directory. In this article, we’ll look at one reason why this could happen and see what we can do about it.

## Where’s my Data?

As a quick refresher, here’s the test we wrote at the end of last week’s newsletter. Instead of putting the expected text directly in the test, we moved it to a separate file and pass its file name (`CustomerWelcomeEmail.txt`) as a relative path into `File.ReadAllText` to read it.

``` csharp
[Test]
public void RegistrationEmailIsSent()
{
    // Arrange

    var emailService = Mock.Of<IEmailService>();
    var registrationService = new
        UserRegistrationService(emailService);

    // Act

    registrationService.RegisterNewUser("Test User");

    // Assert

    var expected = File.ReadAllText("CustomerWelcomeEmail.txt");
    Mock.Get(emailService).Verify(s => s.SendEmail(expected));
}
```

This works with the default test runner in Visual Studio, but not every host works in the same way. The built-in tools in Visual Studio 2022 (the latest version at time of writing) are excellent. But previous versions offered a more persuasive argument to use (the also excellent) JetBrains [ReSharper](https://www.jetbrains.com/resharper/) to help fill the gaps.

ReSharper has its own test runner that copies tests to a temporary directory while running them (or at least that was the case in the version I had previously used). However, it didn’t copy any additional files along with them; this meant that the relative path was no longer valid. But more importantly, I’ve also seen this happen on build servers/CI pipelines too.

Unfortunately, we can’t (or shouldn’t) solve this by specifying an absolute path to the resource. It’d work on a local development machine but would most likely fail everywhere else on account of not having an identical file structure.

## Bringing it Together

Luckily, there’s a way we can make it work. And we don’t have to find and copy files around either. Instead, we can make our resources an inseparable part of our test assemblies. To do this:

* Right-click on the file.

* Select _Properties_ on the context menu.

* Set the _Build Action_ in the _Properties_ tool window to _Embedded resource_.

Then we need to change our test to read resources from its own assembly rather than from disk. The code to do this is in the `ReadEmbeddedResource` method in the following example.

``` csharp
[Test]
public void RegistrationEmailIsSent()
{
    // Arrange

    var emailService = Mock.Of<IEmailService>();
    var registrationService = new
        UserRegistrationService(emailService);

    // Act

    registrationService.RegisterNewUser("Test User");

    // Assert

    var expected = ReadEmbeddedResource(
        "TestProject.CustomerWelcomeEmail.txt");

    Mock.Get(emailService).Verify(s => s.SendEmail(expected));
}

private static string ReadEmbeddedResource(string resourceName)
{
    var assembly = Assembly.GetExecutingAssembly();
    using var stream =
        assembly.GetManifestResourceStream(resourceName);
    using var reader = new StreamReader(stream);
    var result = reader.ReadToEnd();
    return result;
}
```

Here we’ve passed `TestProject.CustomerWelcomeEmail.txt` as the resource name:

* `TestProject` is our project’s name.

* `CustomerWelcomeEmail.txt` is the name of our resource file.

In this example, our resource file was at the root level of our test project (alongside the file containing our test). If we had added more directories for structure, we’d need to include their names too. For example, if we added `CustomerWelcomeEmail.txt` to a folder called `Resources`, we would want to read `TestProject.Resources.CustomerWelcomeEmail.txt` from our assembly.

## Summary

Tests that load files from disk might pass during development but fail on other computers/CI pipelines if the files can’t be found. It’s safer to embed them instead so they can always be available.

You can do this by changing the _Build Action_ of any file you want embedded to _Embedded resource_. Any tests that use them can then gain access through their own assembly. Adding a helper method for this can be helpful as it both keeps your code tidy, and lets you call it from multiple tests.

---

Thanks for reading!

This article is from my [newsletter](https://webdeveloperdiary.substack.com/?sd=pf). If you found it useful, please consider subscribing. You’ll get more articles like this delivered straight to your inbox (once per week), plus bonus developer tips too!