Reuse Configuration Through a Profile¶
Problem¶
Every test in a project needs the same baseline IConfiguration — copying the same ConfigurationBuilder/AddInMemoryCollection call into every test class is repetitive and drifts inconsistent over time.
Solution¶
using Microsoft.Extensions.Configuration;
public sealed class AppConfigurationProfile : ICompositionProfile
{
public void Configure(CompositionBuilder builder) => builder
.Register<IConfiguration>(() => new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>
{
["Retry:MaxAttempts"] = "3",
["Retry:DelayMilliseconds"] = "100",
})
.Build());
}
[Theory]
[Compose<AppConfigurationProfile>]
public void ReadsTheSharedBaselineConfiguration(IConfiguration configuration)
{
configuration["Retry:MaxAttempts"].Should().Be("3");
}
Or applied to a hand-built Composer directly:
Discussion¶
ICompositionProfile.Configure is just an ordinary sequence of CompositionBuilder calls — Register<IConfiguration> needs no special profile-only mechanism to be reusable this way, the same as any other registration.
Registering the same exact type more than once is a build-time conflict in Compono, not last-write-wins or first-write-wins (docs/adr/0019-registrations-and-service-provider-injection.md) — so a test can't compose AppConfigurationProfile (which already calls Register<IConfiguration>) and then call Register<IConfiguration> again itself to layer an override; that collides and throws CompositionConfigurationException. A test that needs the shared baseline plus one local override instead passes the override into the profile's own constructor and applies it with the instance-based AddProfile(ICompositionProfile) overload, so the profile itself is the one and only place that calls Register<IConfiguration>:
public sealed class AppConfigurationProfile(IDictionary<string, string?>? overrides = null) : ICompositionProfile
{
public void Configure(CompositionBuilder builder) => builder
.Register<IConfiguration>(() => BuildConfiguration(overrides));
}
var composer = Composer.Create(builder => builder.AddProfile(
new AppConfigurationProfile(new Dictionary<string, string?> { ["Retry:MaxAttempts"] = "0" })));
See Layer Configuration Overrides in a Test for BuildConfiguration's own layering (multiple AddInMemoryCollection calls inside one ConfigurationBuilder, not multiple Register<T> calls), and Composition Profiles for the full profile mechanics.