26

I want to store a database connection string for my integration tests as a user secret. My project.json looks like this:

{
  ...

  "dependencies": {
    ...
    "Microsoft.Extensions.Configuration.UserSecrets": "1.1.0"        
  },

  "tools": {
    "Microsoft.Extensions.SecretManager.Tools": "1.1.0-preview4-final"
  },

  "userSecretsId": "dc5b4f9c-8b0e-4b99-9813-c86ce80c39e6"
}

I've added the following to the constructor of my test class:

IConfigurationBuilder configurationBuilder = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .AddUserSecrets();

However when I run the tests the following exception is thrown when it hits that line:

An exception of type 'System.InvalidOperationException' occurred in Microsoft.Extensions.Configuration.UserSecrets.dll but was not handled in user code

Additional information: Could not find 'UserSecretsIdAttribute' on assembly 'dotnet-test-nunit, Version=3.4.0.0, Culture=neutral, PublicKeyToken=null'.

Have I missed something or is what I'm trying to do not supported?

Paul Hunt
  • 3,265
  • 2
  • 23
  • 36

4 Answers4

23

See instructions in https://patrickhuber.github.io/2017/07/26/avoid-secrets-in-dot-net-core-tests.html, in particular in InitialiseTest add

// the type specified here is just so the secrets library can 
            // find the UserSecretId we added in the csproj file
            var builder = new ConfigurationBuilder()
                .AddUserSecrets<HttpClientTests>();

            Configuration = builder.Build()

However note that it will not allow to run tests on build server

Michael Freidgeim
  • 23,917
  • 16
  • 136
  • 163
  • Make sure you use the `Microsoft.Extensions.Configuration.UserSecrets` `nuget package` to be able to use the `AddUserSecrets` method. – Aage Feb 17 '22 at 09:24
6

You must specify the UserSecretsId in Startup of your application.

[assembly: UserSecretsId("xxx")]
namespace myapp
{
    public class Startup
    {
    ...

Then you have to use the overload of .AddUserSecrets(Assembly assembly) in your test project. Example:

.AddUserSecrets(typeof(Startup).GetTypeInfo().Assembly)

Source: https://stackoverflow.com/a/40775511/5270073

Community
  • 1
  • 1
Ricardo Fontana
  • 4,213
  • 1
  • 18
  • 32
0

My base test class initializes the ConfigurationBuilder, so knowing the assembly which has the userSecretsId is more tricky.

However we can determine all the assemblies invoked along the way, as follows

    var builder = new ConfigurationBuilder()
        // NOTE: Make the appsettings optional since we might just have a appsettings.TestConfig
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{environment}.json", optional: true)
        // NOTE: This brings in the test assembly's own settings as overrides for the base and environment values
        .AddJsonFile($"appsettings.TestConfig.json", optional: true)
        .AddEnvironmentVariables();
        
    var currentAssembly = Assembly.GetExecutingAssembly();
    var callerAssemblies = new StackTrace().GetFrames()
        .Select(x => x.GetMethod().ReflectedType.Assembly).Distinct()
        .Where(x => x.GetReferencedAssemblies().Any(y => y.FullName == currentAssembly.FullName));

    UserSecretsIdAttribute attribute = null;
    foreach (var assembly in callerAssemblies)
    {
        attribute = assembly.GetCustomAttribute<UserSecretsIdAttribute>();
        if (attribute != null)
        {
            break;
        }
    }

    if (attribute != null)
    {
        var userSecrets = attribute.UserSecretsId;

        // Wire up user secrets if we have them
        if (!string.IsNullOrEmpty(userSecrets))
        {
#if NETSTANDARD2_0
            builder.AddUserSecrets(userSecrets);
#else
            builder.AddUserSecrets(userSecrets, false);
#endif
        }
    }

Advantage of this in my scenario is that if the developer assigns user secrets the tests will run locally correctly without any code change on their behalf.

Paul Hatcher
  • 6,151
  • 1
  • 49
  • 46
-4

For settings you can use appsettings.json, not the project.json. It looks like this:

{
    "userSecretsId": "dc5b4f9c-8b0e-4b99-9813-c86ce80c39e6"
}

Make sure to copy the file to output by changing the project.json:

"buildOptions": {
    "copyToOutput": "appsettings.json"
}

Now you can retrieve the secret like this:

[Fact]
public MyTest()
{
    var appSettings = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("appsettings.json")
        .Build();

    var secret = appSettings["userSecretsId"]
}
kloarubeek
  • 2,558
  • 18
  • 23
  • 2
    This just seems to tell me how to use secrets in an ASP.NET project which is not what I'm doing here. I already have a separate web project in which I'm successfully using secrets, what I'm asking about is a class library test project. – Paul Hunt Jan 31 '17 at 08:27
  • Sorry, I overlooked that one, I'll see if there's a solution for that. – kloarubeek Jan 31 '17 at 10:07
  • this one should do the trick :-). I think you forgot the copyToOutput. – kloarubeek Jan 31 '17 at 10:14