Skip to main content
CodingPipe.com

Test-specific AppSettings configuration in ASP.NET Core integration tests

Integration tests that use production configuration values are accidents waiting to happen. The TestServer defaults to your main application's appsettings.json, which means tests might hit production databases or external APIs.

Here's how to configure tests to use their own configuration files.

Setup #

Create a test-specific appsettings.json in your test project:

{
  "ConnectionStrings": {
    "DefaultConnection": "Host=localhost;Port=5432;Database=TestDb;Username=postgres;Password=test123"
  },
  "ApiToken": "test-api-key-456"
}

Set the file properties to Build Action: Content and Copy to Output Directory: Copy if newer.

Add this minimal API endpoint to your main app for testing:

app.MapGet("/api-token", (IConfiguration config) => config["ApiToken"]);

Configure your WebApplicationFactory to use the test configuration:

using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;

namespace PlaygroundApi.Tests
{
    public class IntegrationTests
    {
        private readonly WebApplicationFactory<Program> _webAppFactory;

        public IntegrationTests()
        {
            _webAppFactory = new WebApplicationFactory<Program>()
                .WithWebHostBuilder(builder =>
                {
                    builder.ConfigureAppConfiguration((context, config) =>
                    {
                        config.Sources.Clear();
                        config.SetBasePath(Directory.GetCurrentDirectory());
                        config.AddJsonFile("appsettings.json");
                    });
                });
        }

        [Fact]
        public async Task ShouldUseTestConfiguration()
        {
            var httpClient = _webAppFactory.CreateClient();
            var result = await httpClient.GetStringAsync("/api-token");
            
            Assert.Equal("test-api-key-456", result);
        }
    }
}

Dynamic Configuration Override #

For Testcontainers or other runtime configuration needs:

public class ContainerizedTests : IAsyncLifetime
{
    private readonly PostgreSqlContainer _postgreSqlContainer = new PostgreSqlBuilder().Build();
    private WebApplicationFactory<Program> _webAppFactory;

    public async Task InitializeAsync()
    {
        await _postgreSqlContainer.StartAsync();
        
        _webAppFactory = new WebApplicationFactory<Program>()
            .WithWebHostBuilder(builder =>
            {
                builder.ConfigureAppConfiguration((context, config) =>
                {
                    config.Sources.Clear();
                    config.SetBasePath(Directory.GetCurrentDirectory());
                    config.AddJsonFile("appsettings.json");
                    
                    // Override with container connection string
                    config.AddInMemoryCollection(new Dictionary<string, string>
                    {
                        ["ConnectionStrings:DefaultConnection"] = _postgreSqlContainer.GetConnectionString()
                    });
                });
            });
    }

    [Fact]
    public async Task ShouldConnectToTestContainer()
    {
        var httpClient = _webAppFactory.CreateClient();
        
        // Your test logic here using the containerized database
    }

    public async Task DisposeAsync()
    {
        await _postgreSqlContainer.DisposeAsync();
        _webAppFactory?.Dispose();
    }
}

Why This Works #

config.Sources.Clear() prevents inheriting production configuration. SetBasePath(Directory.GetCurrentDirectory()) points to the test output directory where your test appsettings.json gets copied. This approach keeps test configuration explicit and version-controlled rather than relying on environment variables or hardcoded values.