Append test project appSettings to ASP.NET Core integration tests
Asked Answered
Y

3

7

I'm creating ASP.NET Core integration tests (xUnit based) following these docs. I want to start the test web server with its own appsettings.json. My abbreviated folder structure is:

\SampleAspNetWithEfCore
\SampleAspNetWithEfCore\SampleAspNetWithEfCore.csproj
\SampleAspNetWithEfCore\Startup.cs
\SampleAspNetWithEfCore\appsettings.json
\SampleAspNetWithEfCore\Controllers\*

\SampleAspNetWithEfCore.Tests\SampleAspNetWithEfCore.Tests.csproj
\SampleAspNetWithEfCore.Tests\IntegrationTests.cs
\SampleAspNetWithEfCore.Tests\appsettings.json

then I have these utilities:

public static class ServicesExtensions
{
    public static T AddOptions<T>(this IServiceCollection services, IConfigurationSection section)
        where T : class, new()
    {
        services.Configure<T>(section);
        services.AddSingleton(provider => provider.GetRequiredService<IOptions<T>>().Value);

        return section.Get<T>();
    }
}

and inside Startup.cs ConfigureServices(...) I do this:

services.AddOptions<SystemOptions>(Configuration.GetSection("System"));

Referring to the appsettings.json section like this:

"System": {
  "PingMessageSuffix": " suffix-from-actual-project"
}

So far so good: this is picked up in a strongly typed manner. My controller gets a SystemOptions instance that mirrors the json structure, and the controller uses the suffix correctly.

The problems are with building the Integration Tests WebHost. I want to run the Startup from my real project as is, with its own appsettings.json settings, but as an extra layer of settings I want the appsettings.json from my test csproj to be added, overriding any settings if applicable. This is my appsettings from the test project:

"System": {
  "PingMessageSuffix": " suffix-from-test-appsettings"
}

Here's what I've tried:

public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder
            .UseStartup<Startup>()
            .ConfigureAppConfiguration(config => config
                .AddJsonFile("appsettings.json")
            );
    }
}

However, this doesn't work. If I hit a breakpoint in my controller I see only the settings from the base project. The controller just echo's the config value currently, and logically the return result is also not as expected.

The documentation doesn't mention "appsettings" anywhere on the page.

Bottom line: How can you add a layer of appSettings from a test project's appsettings.json file when running ASP.NET Core integration tests?

Yearround answered 10/12, 2018 at 11:9 Comment(6)
You can add multiple settings files and configuration providers with later ones overriding the settings from previous ones. You could have eg .AddJsonFile(..).AddJsonFile(appsettings.test.json") to override settings just for tests, or add command-line, environment variable providers to override settings for specific runs or machinesOtisotitis
But wouldn't that require me to change the actual project? I don't want to do that, I want my Test project to handle overriding the available settings, not the other way around.Yearround
nothing says the test project can't have its own configuration method. If you have multiple project you probably have separate config methods already. In any case, if you check most tutorials they show using different files based on environment variables, overriding etc. The prod, test, integration files can be optional. The Environment, Command line providers don't need any special treatment either.Otisotitis
Besides, if you think of moving to containers, environment variables are one of the best ways of modifying config per container withouth rebuildingOtisotitis
Ok, makes sense. But I'm struggling how to change the ConfigureWebHost override to use the test project's appSettings...Yearround
@PanagiotisKanavos Thanks again for you input. It seems the MSDN authors to some degree agree with your suggestions (see footnote in my answer). I did find a way to push through with my original method, which is a more direct answer to my own question, so I posted it below FWIW..Yearround
Y
12

Solved it like this:

  1. For appsettings.json in the Test project set the Properties:
    • Build Action to Content
    • Copy to Output Directory to Copy if newer
  2. Use a custom WebApplicationFactory like so:

    public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            var configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .Build();
    
            // Note:         ↓↓↓↓
            builder.ConfigureTestServices(services => 
                services.AddOptions<SystemOptions>(configuration.GetSection("System"))
            );
        }
    }
    

And voila: it works!

The first step is needed to make the ConfigurationBuilder find your json file easily. The second step subtly uses a ...TestServices configuration (if you use the regular ConfigureServices method it'll be called before the Startup's service configuration and get overwritten).

Footnote: commenters (on the question) have mentioned it might be better to have a appsettings.ci.json file in the SUT project, and control things by environment (which you'd set via launch settings or via the WebHostBuilder). The documentation links to a few closed GitHub issues that suggest the same thing: 8712, 9060, 7153. Depending on your scenario and taste, that might be a better or more idiomatic solution.

Yearround answered 10/12, 2018 at 15:43 Comment(2)
OMG - those down arrows are awesome! (yeah, i know ... it's the little things ...)Ingridingrim
Still working like a charm in .Net 5. Since this is how the appsettings.json files are configured in a typical project template this just makes sense.Burgener
A
5

Update Feb 2020 - ASP.NET Core 3.0 and above

The way you do this has changed, you need to use the ConfigureAppConfiguration delegate.

public class HomeControllerTests : IClassFixture<WebApplicationFactory<Startup>>
{
    private readonly WebApplicationFactory<Startup> _factory;

    public HomeControllerTests(WebApplicationFactory<Startup> factory)
    {
        var projectDir = Directory.GetCurrentDirectory();
        var configPath = Path.Combine(projectDir, "appsettings.json");

         //New              ↓↓↓              
        _factory = factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureAppConfiguration((context,conf) =>
            {
                conf.AddJsonFile(configPath);
            });

        });
     }
}

Credit to: https://gunnarpeipman.com/aspnet-core-integration-tests-appsettings/

Ayeaye answered 26/2, 2020 at 20:19 Comment(4)
does it work this way ? i believe this is not working for me !! always main appsettings from project file is picked up. My current environment is pointing to Testing, with env variable name matching the appsettings.env.json. However my integration test should pick the local appsettings that i do using the code in the link !! ANy suggestions?Reveal
What version of ASP.NET Core are you using? This is version 3.0 and above.Ayeaye
While this should work, the accepted answer works in .net 5 and I'm sure .net 3. Simply setting the Build Action and Copy to Output Directory seem like the way to go here bc that's the standard approach in any project template.Burgener
Indeed this doesn't work. I'm using it with .AddInMemoryCollection but new settings aren't visible to the code and settings from appsettings.json aren't overridden. The action parameter to ConfigureAppConfiguration is never called.Ibeam
U
0

Both Unit test projects and integration test projects will require their own appsettings. We call ours appsettings.Test.json

Then we use the Microsoft.Extensions.Configuration to access them. So typically in a test I will do something like:

 private readonly IConfiguration _configuration;

    public HomeControllerTests()
    {
          _configuration = new ConfigurationBuilder()
           .AddJsonFile("appsettings.Test.json")
           .Build();
    }

then a reference to a value in this file by:

_configuration["user:emailAddress"]

where the file looks like:

"user": { "emailAddress": "myemail.com", …...

For what you are trying to do you will probably need to create an appsettings.test.json file similar to mine that sits alongside your main apsettings file. You'll then want to test the environment and then add the appropritate appsettings file.

Unrestrained answered 10/12, 2018 at 11:27 Comment(1)
Thanks for your response. This doesn't work for me though (unless it's the only workaround), as I'd like to use the Options pattern (so no direct references to configuration) bootstrapped in Startup. I also want to use a TestFixture as in linked documentation, since that seems to be the idomatic way to do things.Yearround

© 2022 - 2024 — McMap. All rights reserved.