How to use user secrets in a dotnet core test project
Asked Answered
S

5

34

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?

Sloat answered 30/1, 2017 at 17:6 Comment(0)
C
33

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

Clavicorn answered 16/12, 2017 at 10:43 Comment(1)
Make sure you use the Microsoft.Extensions.Configuration.UserSecrets nuget package to be able to use the AddUserSecrets method.Pycnometer
T
8

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://mcmap.net/q/451877/-could-not-find-39-usersecretsidattribute-39-on-assembly-39-ef-39

Turn answered 21/2, 2017 at 12:34 Comment(0)
W
4

The other posts listed here have some great help for doing it all manually but VS2022 has automated it for you.

In Visual Studio 2022 just right click on the test project name and go to "Manage User Secrets".

If you have not installed Microsoft.Extensions.Configuration.UserSecrets it will prompt you that "...dependencies are needed. Would you like to add them..."

Click Yes and the secrets.json file will open up and you are off to the races.

(If you already have the UserSecrets installed it just opens up secrets.json.)

Here is a code snippet that worked great for me.

using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Project.Cosmos;

namespace Project.Model.Tests;

public class CosmosDbRepository
    {
    private IConfiguration Configuration { get; }

    public CosmosDbRepository()
        {
        var builder = new ConfigurationBuilder()
            .AddUserSecrets< CosmosDbRepository >();

        Configuration = builder.Build();
        }

    [Fact]
    public async Task Should_connect_to_cosmos_db()
        {
        // Arrange
        var uri = Configuration[ "Cosmos:Uri" ];
        var key = Configuration[ "Cosmos:Key" ];
        var _repo = await CosmosRepository.CreateRepo( uri, key );
        // Act
        var account = await _repo.Client.ReadAccountAsync();
        // Assert
        account.Should().NotBeNull( "A valid connection should be able to query its account." );
        }
    }

With a secrets.json of

{ "Cosmos": { "Uri": "Your Value Here", "Key": "Your Value Here" } }

Whitman answered 23/10, 2022 at 7:38 Comment(0)
I
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
            // NOTE: Your choice as to whether the secrets are optional or not
            builder.AddUserSecrets(userSecrets, true);
#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.

Idette answered 18/12, 2021 at 12:44 Comment(3)
That raises System.IO.NotFoundException: 'The configuration file 'secrets.json' was not found and is not optional. The physical path is 'c:\project-path'.Regressive
To create the file you need to right-click on the project and select Manage User Secrets, this will create an empty file in the correct locationIdette
Updated the sample code to make secrets optional, breaking change in .NET 6.0, see github.com/dotnet/runtime/issues/61418Idette
B
-5

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"]
}
Brnaba answered 30/1, 2017 at 21:15 Comment(3)
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.Sloat
Sorry, I overlooked that one, I'll see if there's a solution for that.Brnaba
this one should do the trick :-). I think you forgot the copyToOutput.Brnaba

© 2022 - 2024 — McMap. All rights reserved.