Fix ConfigureTestConfiguration being invoked twice#5203
Conversation
ConfigureTestConfiguration was passed to GetIsolatedFactory both as startup configuration (applied via UseSetting before Program.cs) and as app configuration (applied via ConfigureAppConfiguration after Program.cs), causing it to run twice — once before and once after Factory.ConfigureWebHost. Remove the redundant startup configuration parameter so ConfigureTestConfiguration only runs once, at the correct point in the lifecycle (after factory configuration, allowing test overrides).
There was a problem hiding this comment.
Code Review
Summary
This is a clean, well-scoped fix for a genuine double-invocation bug in the lifecycle. The root cause is clear, the fix is surgical, and the documentation + tests are updated in lockstep.
The Core Fix
The bug in TestWebApplicationFactory.cs was straightforward: ConfigureTestConfiguration was passed twice to GetIsolatedFactory:
- As
configureIsolatedStartupConfiguration→ applied viaUseSetting(pre-startup, beforeProgram.cs) - As
configureIsolatedAppConfiguration→ applied viaConfigureAppConfiguration(post-factory, at app-startup time)
Removing the first pass is the right call. The semantically correct place for test overrides is as ConfigureAppConfiguration, after the factory has had its chance to configure the host.
Execution Order Clarification (No Action Required — Just Worth Knowing)
The new documented order (5 = ConfigureWebHostBuilder, 6 = ConfigureTestConfiguration) maps to two different phases of execution:
configureWebHostBuilder?.Invoke(builder)is called synchronously at the end of theWithWebHostBuilderlambda (factory-setup time)ConfigureAppConfigurationcallbacks, includingconfigureIsolatedAppConfiguration, run at app-startup time
This means ConfigureWebHostBuilder code runs at factory-construction time, while ConfigureTestConfiguration runs during application startup — which is precisely what makes test config able to override factory config. The ordering is correct and the docs now accurately reflect this.
One subtle implication: if a user calls builder.ConfigureAppConfiguration(...) inside their ConfigureWebHostBuilder override, that registration would be added after configureIsolatedAppConfiguration was registered — so it would execute after ConfigureTestConfiguration at app-startup time. This is likely the intended "escape hatch" behavior (last word goes to the escape hatch), but it's worth a comment in the code or docs if users ever encounter confusion here.
TestsBase.cs Cleanup
Removing the if (ConfigureTestConfigurationCalledOrder == 0) guard is exactly right — it was a workaround for a bug that no longer exists. Cleaning it up avoids misleading future readers about why the guard was there.
Minor Observation
The configureWebHostBuilder?.Invoke(builder) call is at the end of the WithWebHostBuilder lambda, after ConfigureAppConfiguration and ConfigureTestServices registrations. This is intentional and correct — it ensures that by the time user code runs in ConfigureWebHostBuilder, all the standard registrations are already in place. This is good design; just noting it's load-bearing.
Verdict
LGTM. The fix is minimal, correct, and properly validated. The documentation and test updates are accurate and helpful. No architectural concerns.
There was a problem hiding this comment.
Code Review
Summary
This is a clean, well-scoped fix for a genuine double-invocation bug in the ConfigureTestConfiguration lifecycle. The root cause is clear, the fix is surgical, and the documentation + tests are updated in lockstep.
The Core Fix
The bug in TestWebApplicationFactory.cs was straightforward: ConfigureTestConfiguration was passed twice to GetIsolatedFactory:
- As
configureIsolatedStartupConfigurationapplied viaUseSetting(pre-startup, beforeProgram.cs) - As
configureIsolatedAppConfigurationapplied viaConfigureAppConfiguration(post-factory, at app-startup time)
Removing the first pass is the right call. The semantically correct place for test overrides is as ConfigureAppConfiguration, after the factory has had its chance to configure the host.
Execution Order Clarification (No Action Required, Worth Knowing)
The new documented order (5 = ConfigureWebHostBuilder, 6 = ConfigureTestConfiguration) maps to two different phases of execution:
configureWebHostBuilder?.Invoke(builder)is called synchronously at the end of theWithWebHostBuilderlambda (factory-setup time)ConfigureAppConfigurationcallbacks, includingconfigureIsolatedAppConfiguration, run at app-startup time
This means ConfigureWebHostBuilder code runs at factory-construction time, while ConfigureTestConfiguration runs during application startup, which is precisely what makes test config able to override factory config. The ordering is correct and the docs now accurately reflect this.
One subtle implication: if a user calls builder.ConfigureAppConfiguration(...) inside their ConfigureWebHostBuilder override, that registration is added after configureIsolatedAppConfiguration was registered, so it would execute after ConfigureTestConfiguration at app-startup time. This is likely the intended "escape hatch" behavior (last word goes to the escape hatch), but it may be worth a doc note if users ever encounter confusion here.
TestsBase.cs Cleanup
Removing the if (ConfigureTestConfigurationCalledOrder == 0) guard is exactly right. It was a workaround for a bug that no longer exists. Leaving it would mislead future readers into thinking there is still an expected double-invocation.
Verdict
LGTM. The fix is minimal, correct, and properly validated. The documentation and test updates are accurate and consistent with the actual execution model. No architectural concerns.
Summary
Fixes #5195
ConfigureTestConfigurationwas passed toGetIsolatedFactorytwice: once as startup configuration (applied viaUseSettingbeforeProgram.cs) and once as app configuration (applied viaConfigureAppConfigurationafterProgram.cs). This caused the method to run twice — once before and once afterFactory.ConfigureWebHost— breaking the documented lifecycle order.configureIsolatedStartupConfigurationparameter fromGetIsolatedFactorysoConfigureTestConfigurationonly runs once, at the correct point in the lifecycle (after factory configuration, allowing test overrides).Test plan
TUnit.Example.Asp.Net.TestProjectlifecycle order tests pass (especiallyFactoryMethodOrderTests.Full_Relative_Order)ConfigureTestConfigurationis only invoked once per test