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.