-
-
Notifications
You must be signed in to change notification settings - Fork 109
feat: enhance test configuration isolation and validation in WebApplicationFactory #4157
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using Microsoft.AspNetCore.TestHost; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using Microsoft.Extensions.Configuration; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using Microsoft.Extensions.DependencyInjection; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using Microsoft.Extensions.Hosting; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using TUnit.AspNetCore.Extensions; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using TUnit.AspNetCore.Interception; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using TUnit.Core; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -14,31 +15,53 @@ namespace TUnit.AspNetCore; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public abstract class TestWebApplicationFactory<TEntryPoint> : WebApplicationFactory<TEntryPoint> where TEntryPoint : class | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public WebApplicationFactory<TEntryPoint> GetIsolatedFactory( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TestContext testContext, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| WebApplicationTestOptions options, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Action<IServiceCollection> configureServices, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Action<WebHostBuilderContext, IConfigurationBuilder> configureConfiguration, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Action<IServiceCollection> configureIsolatedServices, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Action<IConfigurationBuilder> configureIsolatedStartupConfiguration, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Action<WebHostBuilderContext, IConfigurationBuilder> configureIsolatedAppConfiguration, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Action<IWebHostBuilder>? configureWebHostBuilder = null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return WithWebHostBuilder(builder => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Apply user's escape hatch configuration first | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| configureWebHostBuilder?.Invoke(builder); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var configurationBuilder = new ConfigurationManager(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ConfigureStartupConfiguration(configurationBuilder); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ConfigureStartupConfiguration(configurationBuilder); |
Copilot
AI
Dec 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The configureWebHostBuilder escape hatch is now called AFTER the standard configuration (services, configuration, HTTP exchange capture). The PR description mentions "enhance test configuration isolation" but this changes the order compared to the comment on the removed line 29 which said "Apply user's escape hatch configuration first".
This is a subtle behavioral change: previously the escape hatch ran first and could be overridden by standard configuration; now it runs last and can override standard configuration. While the new order may be more useful (allowing tests to override framework defaults), this is a breaking change that should be:
- Documented in the method's XML comments
- Called out in the PR description or release notes
- Verified against existing tests that may rely on the old order
Consider whether this ordering change is intentional and desired.
Copilot
AI
Dec 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new ConfigureStartupConfiguration method should have XML documentation comments to explain:
- Its purpose and when it's called
- How it differs from
ConfigureTestConfiguration(in WebApplicationTest) - That it's called during host builder creation, before the application starts
- An example of when to use it versus app configuration
This is especially important since this is part of the public API surface that test authors will extend.
| /// <summary> | |
| /// Configures host-level startup configuration for tests that use this factory. | |
| /// </summary> | |
| /// <remarks> | |
| /// <para> | |
| /// This method is invoked during creation of the generic host builder, before the | |
| /// application startup code runs. Values added to <paramref name="configurationBuilder"/> | |
| /// become part of the host configuration and can be overridden by later configuration | |
| /// sources in the application configuration pipeline. | |
| /// </para> | |
| /// <para> | |
| /// Unlike <c>ConfigureTestConfiguration</c> on <c>WebApplicationTest</c>, which configures | |
| /// the application configuration (for example via | |
| /// <see cref="IWebHostBuilder.ConfigureAppConfiguration(System.Action{WebHostBuilderContext, IConfigurationBuilder})"/>) for a | |
| /// specific test run, this method is intended for host-level or global defaults that | |
| /// apply to all tests using this <see cref="TestWebApplicationFactory{TEntryPoint}"/>. | |
| /// It is called as part of <see cref="IHostBuilder.ConfigureHostConfiguration(System.Action{IConfigurationBuilder})"/>. | |
| /// </para> | |
| /// <para> | |
| /// Use this method when you need to establish baseline configuration for the test host, | |
| /// such as default environment variables, in-memory key/value pairs, or connection strings | |
| /// that should be present for every test. For per-test or scenario-specific configuration, | |
| /// prefer using the <c>configureIsolatedStartupConfiguration</c> or | |
| /// <c>configureIsolatedAppConfiguration</c> delegates passed to | |
| /// <see cref="GetIsolatedFactory(TestContext, WebApplicationTestOptions, System.Action{IServiceCollection}, System.Action{IConfigurationBuilder}, System.Action{WebHostBuilderContext, IConfigurationBuilder}, System.Action{IWebHostBuilder}?)"/> | |
| /// or application-level hooks such as <c>ConfigureTestConfiguration</c>. | |
| /// </para> | |
| /// </remarks> | |
| /// <param name="configurationBuilder"> | |
| /// The <see cref="IConfigurationBuilder"/> used to construct the host configuration | |
| /// before the application starts. | |
| /// </param> |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -115,6 +115,7 @@ public async Task InitializeFactoryAsync(TestContext testContext) | |||||
| testContext, | ||||||
| _options, | ||||||
| ConfigureTestServices, | ||||||
| ConfigureTestConfiguration, | ||||||
|
||||||
| ConfigureTestConfiguration, | |
| _ => { }, |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,6 +5,11 @@ | |||||||
|
|
||||||||
| var builder = WebApplication.CreateBuilder(args); | ||||||||
|
|
||||||||
| if (builder.Configuration["SomeKey"] != "SomeValue") | ||||||||
|
||||||||
| if (builder.Configuration["SomeKey"] != "SomeValue") | |
| if (builder.Environment.IsDevelopment() && | |
| builder.Configuration["SomeKey"] != "SomeValue") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Disposable 'ConfigurationManager' is created but not disposed.