Should GetEnvironmentVariable work in xUnit tests?
Asked Answered
O

3

65

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.

Owensby answered 12/5, 2017 at 1:31 Comment(2)
Please show how you set environment variables for a test project.Crazy
@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.Owensby
C
118

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.

Crazy answered 13/5, 2017 at 9:0 Comment(12)
Fantastic solution @Ilya. Thank you for posting. I did not even know about the launchSettings.json file.Owensby
@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.Crazy
@IlyaChumakov Do you know how can I get the current profile? So I can filter just the variables for the profile is running the testsCivility
This was such a useful bit of code! Thanks for sharing this!Acth
So irritating why xUnit would invent it's own environment settings. Thanks for getting me unstuck.Justinn
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.Bowerbird
Doesn't look like they've fixed this yet... Thanks for the solutionBarbule
@StephenBuck There's no "invented environment settings" in xUnit.net.Freeze
@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.Freeze
@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.Reactor
@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.Freeze
@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.Crazy
P
11

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

Pooka answered 26/7, 2021 at 13:55 Comment(7)
The original question is about xUnit, but xUnit does not support runsettings files.Haematin
Solution worked with dotnet core SpecFlow BDDMumps
works with NUnit too.Reactor
does not work with xUNITPhotolithography
Works with xUnit just fine.Syringe
xunit doesnt support runsettings.xmlMinistration
learn.microsoft.com/en-us/visualstudio/test/…Bustos
W
3

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.

Wenger answered 20/10, 2022 at 15:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.