Using dotnet IConfiguration inside xUnit projects
Published: 2022-06-19 15:57:32

In a .NET core XUnit project, configuration functionality is not readily available.

My need is using secrets inside UI tests (i.e. for signing in before testing the web app) without committing them into the repository.

I read different solutions and found many third party libraries but in the end I chose a simple approach, a few lines of code easily adaptable to different needs, still functional both for local development and CI/CD environments.

I created a simple [github repo](https://github.com/andreav/xunit-dotnet-iconfig) for demonstration purposes.

Basically there are 2 files:
* appsettings.json - you can commit it, but without sensitive data. Here you put your config, using placeholders for sensitive data.
* appsettings.local.json - you must not commit it, list it in your .gitignore file. Here you put you sensitive data, overriding placeholders from appsettings.json.  

Then you have to copy those files to output directory, adding this to your .csproj:

```xml
    <ItemGroup Condition="'$(Configuration)' == 'Debug'">
        <None Update="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
    </ItemGroup>
    <ItemGroup Condition="'$(Configuration)' == 'Debug'">
        <None Update="appsettings.local.json" CopyToOutputDirectory="PreserveNewest" />
    </ItemGroup>
```

Now the code. 

Create an utilty class (`TestConfigHelper`) for setting up IConfiguration in every test (you can adjust this class as needed, an example will be reported in the following).

This is where you configure the Configuration service:

```cs
public static IConfigurationRoot GetIConfigurationRoot()
{
    return new ConfigurationBuilder()
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile("appsettings.local.json", optional: true, reloadOnChange: true)
        .AddEnvironmentVariables()
        .Build();
}
```

In this code we take out configuration from three places:
* first appsetting.json
* then appsetting.local.json
* in the end from the environment

The order matters. appsetting.local.json will override appsetting.json, and environment variables will override everything.

In this way you can use appsetting.local.json for local development, but also inject sensitive data through environment variables in you CI/CD pipeline.

Your tests should inherit from a BaseTestClass which loads configuration invoking `TestConfigHelper::GetIConfigurationRoot` method.

And that's all. You can now access the configuration using the object returned from `GetIConfigurationRoot` in every test.


# Bonus #1 - configuration class

I like creating a class representing my application configuration, and bind it to the configuration.
`TestConfigHelper` has an example of this. The code is like this (where `MyAppConfig` is the class representing the configuration, stored under "MyApp" root node in the configuration file):

```cs
public static MyAppConfig GetMyAppConfiguration()
{
    var configuration = new MyAppConfig();
    GetIConfigurationRoot().Bind("MyApp", configuration);
    return configuration;
}
```



# Bonus #2: user secrets

Moreover, for local development, you can configure user secrets. This requires some more configuration.

* Creating and setting a secret

```
dotnet user-secrets init
dotnet user-secrets set key value
```
    
* Adding the user secrets key to the `.csproj` file:

```xml
<UserSecretsId>your-secret-id-here</UserSecretsId>
```

* Configuring also Secrets in `TestConfigHelper.cs`, something like this:

```cs
public static IConfigurationRoot GetIConfigurationRoot()
{
    return new ConfigurationBuilder()
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile("appsettings.local.json", optional: true, reloadOnChange: true)
        .AddUserSecrets("e3dfcccf-0cb3-423a-b302-e3e92e95c128")         // <-- this is the new line !!
        .AddEnvironmentVariables()
        .Build();
}
```