8

I am running unit tests as part of an ASP.NET Core MVC solution. The version of .NET Core is 2.1 to be exact.

I have created a profile section in a launchSettings.json file that contains environment variables that I would like to have loaded and injected by the test runner, so that the environment variables are available and can contain specific values when running unit tests.

The launchSettings.json from my ASP.NET Core MVC project is added to my unit test project as a link, and the properties are set to Build Action - None, Copy to output folder - Copy Always.

The file gets copied to my output folder, but I'm not sure how to get the test runner to use this file with the UnitTesting profile. I have tried using the word "Test" as the profile name, nothing seems to work.

Here is a sample launchSettings.json file:

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:62267/",
      "sslPort": 0
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development",
        "MigrationHistoryTableName": "MigrationHistory",
        "ConnectionStringName": "EFConnectionString",
        "Redis__SSL": "True",
        "Redis__Port": "6380",
        "Redis__InstanceName": "RedisDev",
        "Redis__AbortConnect": "False",
        "Redis__ConnectionString": "{URI}:{Port},password={Password},ssl={SSL},abortConnect={AbortConnect}"
      }
    },
    "MyDataServices": {
      "commandName": "Project",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development",
        "MigrationHistoryTableName": "MigrationHistory",
        "ConnectionStringName": "EFConnectionString",
        "Redis__SSL": "True",
        "Redis__Port": "6380",
        "Redis__InstanceName": "RedisDev",
        "Redis__AbortConnect": "False",
        "Redis__ConnectionString": "{URI}:{Port},password={Password},ssl={SSL},abortConnect={AbortConnect}"
      },
      "applicationUrl": "http://localhost:4080/"
    },
    "UnitTesting": {
      "commandName": "Executable",
      "executablePath": "test",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development",
        "MigrationHistoryTableName": "MigrationHistory",
        "ConnectionStringName": "EFConnectionString",
        "Redis__SSL": "True",
        "Redis__Port": "6380",
        "Redis__InstanceName": "RedisDev",
        "Redis__AbortConnect": "False",
        "Redis__ConnectionString": "{URI}:{Port},password={Password},ssl={SSL},abortConnect={AbortConnect}"
      }
    }
  }
}

I understand that by default the launchSettings.json file is ignored in git. We will check in our code with a development version of this file that will contain an example of the settings that are expected to be available in a development environment.

Thank you kindly for taking the time to read this and help out!

Yves Rochon
  • 1,492
  • 16
  • 28

3 Answers3

2

I wrote this static loader for now, but that's not ideal:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace MyNamespaceHere.Services.Common.Utilities
{
    public static class LaunchSettingsLoader
    {
        public static void LaunchSettingsFixture(string launchSettingsPath = "Properties\\launchSettings.json", string profileName = "UnitTesting")
        {
            using (var file = File.OpenText(launchSettingsPath))
            {
                var reader = new JsonTextReader(file);
                var jObject = JObject.Load(reader);

                var allprofiles = jObject
                    .GetValue("profiles", StringComparison.OrdinalIgnoreCase);

                // ideally we use this
                var variables = jObject
                    .GetValue("profiles", StringComparison.OrdinalIgnoreCase)
                    //select a proper profile here
                    .SelectMany(profiles => profiles.Children())
                    //.Where(p => p.Value<String> == profileName)
                    .SelectMany(profile => profile.Children<JProperty>())
                    .Where(prop => prop.Name == "environmentVariables")
                    .SelectMany(prop => prop.Value.Children<JProperty>())
                    .ToList();

                Console.WriteLine(variables?.Count);

                var profilesDictJToken = allprofiles.ToObject<Dictionary<string, JToken>>();
                var unitTestingProfile = profilesDictJToken[profileName];
                var unitTestingProfileDictJToken = unitTestingProfile.ToObject<Dictionary<string, JToken>>();
                var environmentVariables = unitTestingProfileDictJToken["environmentVariables"];
                var environmentVariablesList = environmentVariables.ToList();

                foreach (var variable in environmentVariablesList)
                {
                    var name = ((JProperty)variable).Name;
                    var value = ((JProperty)variable).Value.ToString();
                    Environment.SetEnvironmentVariable(name, value);
                }
            }
        }
    }
}
Yves Rochon
  • 1,492
  • 16
  • 28
  • Did you manage a way to get the current profileName? I have a similar scenario, but I need to get the profileName at runtime – Gabriel Guarnieri Cardoso Dec 07 '18 at 12:56
  • My first attempt was to use the JsonTextReader above and the block of code I found from another post that starts with the line var variables = jObject… But that iterates through all the profiles. The less efficient code that uses Dictionaries near the bottom allows me to narrow it down to one profile who's name is passed via the profileName variable. – Yves Rochon Dec 07 '18 at 13:14
  • Yea, but how do you know if you're running with "UnitTesting" profile or "OtherProfile" ? I mean, how do you get the current running profile – Gabriel Guarnieri Cardoso Dec 07 '18 at 13:17
  • I don't, I explicitly call the method with the path to the .json file and profile name I want to use for the unit test run since I can't figure out how to get the test runner to use the launchSettings.json file automatically. – Yves Rochon Dec 07 '18 at 13:25
  • An even better solution is to use environment variables instead of settings files altogether. This approach works extremely well when containerizing your solution. At design time, you would create a .env file that would contain the actual values for your settings, and you would exclude this file from source control. You could provide a Sample.env file under source control with suggested settings (no secrets of course). For your unit tests, you could load environment variables values from file, or define them explicitly when you arrange your test. See https://www.nuget.org/packages/DotNetEnv. – Yves Rochon Feb 21 '19 at 17:37
  • This is a nice NuGet package @Yves Rochon. My solution was adding an environment variable on the machine like "BUILD_ACTION". And on the test startup, the system reads this variable and loads the correct transform config. But with this NuGet package, the setup could be more clear about what's going on and also similar to react .env configs – Gabriel Guarnieri Cardoso Feb 22 '19 at 09:49
2

I have created a package that might be able to help you: https://www.nuget.org/packages/DotNet.Project.LaunchSettings

You can find the source here: https://github.com/andygjp/DotNet.Project.LaunchSettings

I have a similar use case to you - I want to test different environments and I keep the logon details to these environments in the launchSettings.json.

This is how I use it:

async Task Example()
{
  var launchSettings = VisualStudioLaunchSettings.FromCaller();
  var profiles = launchSettings.GetProfiles();
  var profile = profiles.FirstOrEmpty();

  var client = await AppEnvironment.FromEnvironment(profile.EnvironmentVariables).CreateClient();
  // use the client
}

It assumes launchSettings.json is in the usual place:

MyProject -> Properties -> launchSettings.json

However, I could add the functionality to load launch settings from a specified file path if you would find it useful.

The example above uses the first profile it finds, however, you can also use a particular profile.

Hope it helps.

andygjp
  • 2,464
  • 1
  • 16
  • 11
  • So this library seems to tidily package the code for directly accessing the launch settings. But it doesn't seem to handle the core issue that OP has here, and that I'm seeing in may other places: how to load the current profile. In the example you have in your rep it looks like you're hard coding it. Have you found a way to access the current profile name programatically? – Riplikash Aug 22 '22 at 18:54
1

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

See link below:

https://stackoverflow.com/a/68531200/4593944

Daniel Reis
  • 850
  • 9
  • 10