61

If I set environment variables for a .Net Core web project in Visual Studio 2017 using the project properties page, I can read the value of the variable using Environment.GetEnvironmentVariable; however, when I set the environment variable for my xUnit testing project and then debug the test, Environment.GetEnvironmentVariable always returns null. Is there something about the fact that it is a testing project that should prevent the variable from being used the same as with the web project? If so, is there a way that I can set the environment variables for a test project? Thank you.

Ian Kemp
  • 28,293
  • 19
  • 112
  • 138
Eric
  • 2,120
  • 1
  • 17
  • 34
  • 4
    Please show how you set environment variables for a test project. – Ilya Chumakov May 12 '17 at 08:37
  • 1
    @Ilya, I would add a screenshot if I knew how. In Visual Studio 2017, open the Properties window of the unit testing project. On the Debug tab there is a grid where you can enter Environment Variables. When I add a custom one to my web project, I can read it when I am debugging, but when I set one on the unit testing project, the value is read to be null. – Eric May 12 '17 at 12:14

3 Answers3

110

The GetEnvironmentVariable works fine in xUnit tests. The problem is to properly set a variable. If you set the variable at Properties -> Debug page, then the variable is written to Properties\launchSettings.json and Visual Studio makes all work to launch an application with the selected profile. As you could see, launchSettings.json even isn't copied to output folder by default. It's impossible to pass this file as argument to dotnet run or dotnet test, that leads to obvious problem if tests are run automatically on a CI server. So it is not surprising that launchSettings.json isn't considered by a test runner.

Solution: there are a lot of ways to setup a test environment in xUnit:

For example, this collection fixture sets up all environment variables from launchSettings.json:

public class LaunchSettingsFixture : IDisposable
{
    public LaunchSettingsFixture()
    {
        using (var file = File.OpenText("Properties\\launchSettings.json"))
        {
            var reader = new JsonTextReader(file);
            var jObject = JObject.Load(reader);

            var variables = jObject
                .GetValue("profiles")
                //select a proper profile here
                .SelectMany(profiles => profiles.Children())
                .SelectMany(profile => profile.Children<JProperty>())
                .Where(prop => prop.Name == "environmentVariables")
                .SelectMany(prop => prop.Value.Children<JProperty>())
                .ToList();

            foreach (var variable in variables)
            {
                Environment.SetEnvironmentVariable(variable.Name, variable.Value.ToString());
            }
        }
    }

    public void Dispose()
    {
        // ... clean up
    }
}

Set Copy to output directory: Always for launchSettings.json to make the file accessible from tests.

Ian Kemp
  • 28,293
  • 19
  • 112
  • 138
Ilya Chumakov
  • 23,161
  • 9
  • 86
  • 114
  • Fantastic solution @Ilya. Thank you for posting. I did not even know about the launchSettings.json file. – Eric May 13 '17 at 18:24
  • 1
    @Eric, thanks for good question. Actually we don't use `launchsettings.json` to setup test environment. Instead we create a single simplified json key-value file. It's shared as link for all integration test projects in solution. If you need to keep multiple environments/profiles, consider a config transform like it is done for `appsettings.json` in ASP.NET Core. – Ilya Chumakov May 14 '17 at 14:46
  • 1
    @IlyaChumakov Do you know how can I get the current profile? So I can filter just the variables for the profile is running the tests – Gabriel Guarnieri Cardoso Dec 06 '18 at 16:27
  • This was such a useful bit of code! Thanks for sharing this! – reddi.tech Nov 08 '19 at 04:49
  • 1
    So irritating why xUnit would invent it's own environment settings. Thanks for getting me unstuck. – Stephen Buck Mar 20 '20 at 01:10
  • This is a great solution, but it put me off using environment variables with XUnit. I just don't want to have to explain to everyone why it was necessary. Sad XUnit doesn't deal with this well. – Ben Power Oct 22 '20 at 01:54
  • Doesn't look like they've fixed this yet... Thanks for the solution – Anthony Liriano Nov 17 '20 at 17:28
  • 2
    @StephenBuck There's no "invented environment settings" in xUnit.net. – Brad Wilson Mar 15 '21 at 18:35
  • 3
    @AnthonyLiriano We won't be adding support for a Visual Studio-specific launch file. Why would we? We have no ties to Visual Studio, no requirements for its usage; it's not cross platform, and not even remotely close to the only place people use xUnit.net. – Brad Wilson Mar 15 '21 at 18:36
  • @BradWilson this is one of the most arrogant comment I've seen in a while. probably 95% of .NET devs use VS. This is where your users are. If you choose not to be there, your users will find another test framework that will actually support their needs. – Tsahi Asher Nov 14 '22 at 09:45
  • 1
    @TsahiAsher The framework is opinionated, and it won't be to everybody's tastes. You should pick the framework that best suits your needs. If that's not xUnit.net, then that's fine. If you want `launchSettings.json` to be observed by `dotnet test`, then you can take that up with VSTest, since they're perfectly welcome to add that feature in a framework-independent way, which makes more sense that asking each individual framework to do it. – Brad Wilson Nov 20 '22 at 20:48
  • @TsahiAsher I agree with Brad. Writing this answer back in 2017, I focused on solving the particular problem rather than adding real-world value. I'd like to do it now. Do not misuse any proprietary file, do not rely on it. Such file can be discarded next VS version. You WANT your tests to be isolated from any stored files by default (using options mocking or so). For speed, repeatability etc. With a few exceptions, such as DAL testing. Keep cfg-dependent tests closely monitored, and yes, provide a separate cfg file. Otherwise, one day you may find CI/CD server DDOS-ing the wrong environment. – Ilya Chumakov Apr 21 '23 at 18:36
7

A solution for using environment variables in unit tests, for either mstest or xunittest, is through the ".runsettings" file provided for the platform:

UPDATE: This works only for mstest.

  1. Add a file with .runsettings extension in the project:

Project Structure

  1. Configure environment variables in file "xxx.runsettings" created:
<!-- File name extension must be .runsettings -->
<RunSettings>
  <RunConfiguration>
      <EnvironmentVariables>
          <!-- List of environment variables we want to set-->
          <VARIABLE_XXXX>value X</VARIABLE_XXXX>
          <VARIABLE_YYYY>value Y</VARIABLE_YYYY>
      </EnvironmentVariables>
  </RunConfiguration>
</RunSettings>
  1. Add RunSettingsFilePath tag in test .csproj pointing to the .runsettings file.

Important: the path is absolute.

Using $(MSBuildProjectDirectory) variable will return the absolute path to the project diretory.

enter image description here

Another options to use .runsettings are in link below:

https://learn.microsoft.com/pt-br/visualstudio/test/configure-unit-tests-by-using-a-dot-runsettings-file?view=vs-2019

Daniel Reis
  • 850
  • 9
  • 10
2

Great thread helped me find the adapted solution for me. I needed something that works well for local dev / tests (using VS code) JSON file works fine. I also needed something that can be used with CI/CD and deployment; environment variables were needed. My fixture class needs the environment variable to be able to run the tests in the different environments.

I'm using the same principle @Ilya Chumakov except with System.Text.Json (in 2022 using .NET 6...) I thought that sharing it might help save other people some time:

if (File.Exists(Directory.GetCurrentDirectory()+"/local.settings.json")){
    using var file = File.Open(Directory.GetCurrentDirectory()+ "/local.settings.json",FileMode.Open);
    var document = JsonDocument.Parse(file);
    var variables = document.RootElement.EnumerateObject();
    foreach(var variable in variables){
        Environment.SetEnvironmentVariable(variable.Name, variable.Value.ToString());
    }
}

All the needed variables are at the root of the local.settings.json. adapt if you use it for a lot of things, easy to add an "env" property which contains all your environment variable and just read from it.

For those who use VS code like me. Don't forget to add to your .csproj:

 <ItemGroup>
    <Content Include="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
  </ItemGroup> 

Took me also a bit of tinkering to find that out.