-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Minimal API test config overrides only apply after app has been built #37680
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
Comments
I think the problem here is that you're binding the configuration into your service while the application is still in its bootstrapping phase (which is until Does it work as you'd expect if you change it to this instead? builder.Services.AddSingleton(() => new Options(builder.Configuration["AppSettings:Options"])); I think if you resolve it lazily like that, when the lambda to get |
Yeah it's probably more of a gotcha of the new model - the |
Makes sense. Will update the code and close the issue - thanks! |
Reopening so we document this. This might also be worth trying to fix in .NET 7. Thanks for the great repro. |
@halter73 are you gonna doc this? |
Here's my question on SO on the same issue: https://stackoverflow.com/questions/69986598/with-webapplicationfactory-add-configuration-source-before-program-cs-executes |
I stumbled upon a workaround for this issue. If you override I have not tried it with adding a JSON file but I imagine it should work. Note that calling The comments in (The PR that made Example Program
Example appsettings.json
Example Test
|
The above workaround did not work for me because I wanted the Program.cs:
UnitTest.cs
|
convert api project to minimal hosting model update webapplicationfactory logic to make configuration accessible before webapplicationbuilder build as per: dotnet/aspnetcore#37680
Having the same issue. I use it like this:`
And I have the extension
I don't understand why some of you suggest this is a gotcha rather than a bug. If I set the webhost builder to use environment Test, I would expect the IConfiguration to read from the Also, why do you say it applies only after app has been built? This is my
If anyone has a workaround, please let me know. But I will probably go back to the old fashioned
|
This is blocking my effort to move to new ways of app init too.. Minimal code looks like a total anti-feature. To be able to show of a one-liner for hello world, we have to cope with a lot of new magic that complicates flexibility in unit testing, making it harder to make anything that is a real application.. Before .NET 6 we implement a main method, handle the command line arguments and call into web API init code. That way it is easy for us to make unit tests test everything and provide anything wanted from the unit test libraries into the application to mock, stub, and avoid running code that will blow up if not in production environment. I'm unable to find any documentation of how to convert the minimal code to actual readable normal code which one can integrate with, and I have not been able to find a workaround to make a unit test to be able to provide input readable from the very beginning of the Program.cs file. Going through a static variable of course works, but I want to avoid that as I want multiple unit tests to be able to run in parallel.
All I've managed to do so far is inject a service that can be fetched after builder.Build() has been called, but that is too late.. |
We're going to tackle this in .NET 8, it'll require some new APIs to make it work well. |
Inside
Appears to work as an alternative for the Guidance on this would be appreciated especially if it would be delayed to .NET8 |
Sad times. |
Thanks for contacting us. We're moving this issue to the |
I've faced with similar issue in my tests. I need to use class inherited from This answer prompted me to find workaround for my case:
where
Set this advanced settings for your test project |
So its really great that it is being worked on for .NET 8, but here we are on .NET 7. How is anyone supposed to make test configuration overrides work now? Are we just supposed to abandon the minimal hosting model until .NET 8? None of the above workaround work for me since im calling some extension method on startup that just requires a string value from config. Lazy loading won't help me, the overridden config value needs to be there when the app is starting. Did anyone find a workaround for this? |
I couldn't make it work either, so I abandoned minimal API and keep using the standard Startup + Program. |
So I was able to get this to work with a kind-of-hacky workaround. My Program.cs looks something like this: var app = WebApplication.CreateBuilder(args)
.BuildDefaultConfiguration()
.ConfigureServices((services, configuration) =>
{
//configure IServiceCollection
})
.Build()
.ConfigureApplication(app =>
{
app.UseHealthChecks("/hc", new HealthCheckOptions
{
Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
//define your other routes and what not
});
await app.RunAsync(); and I have the following extension methods defined: public static class BootstrappingExtensions
{
public static WebApplicationBuilder BuildDefaultConfiguration(this WebApplicationBuilder builder)
{
if(builder.Configuration.GetValue("IsConfigured", false)) return builder; // this is the hacky/magic part
//load appsettings.json or Azure App Config or whatever you want
return builder;
}
public static WebApplicationBuilder ConfigureServices(this WebApplicationBuilder builder, Action<IServiceCollection, IConfiguration> configure)
{
configure(builder.Services, builder.Configuration);
return builder;
}
public static WebApplication ConfigureApplication(this WebApplication app, Action<WebApplication> configure)
{
configure(app);
return app;
}
} then in my test fixture I have: [TestFixture]
public sealed class ProgramTest
{
private sealed class ProgramFactory : WebApplicationFactory<Program>
{
protected override IHost CreateHost(IHostBuilder builder)
{
builder.ConfigureHostConfiguration((configBuilder) =>
{
configBuilder.Sources.Clear();
configBuilder.AddInMemoryCollection(new Dictionary<string,string>
{
{ "ConnectionStrings:MyDatabaseConnection", "<some test value>" },
{ "IsConfigured", "true" }
});
});
}
}
[Test]
public async Task Program_HealthCheck_ShouldReturnHealthy()
{
using var application = new ProgramFactory();
using var client = application.CreateClient();
var response = await client.GetAsync("/hc");
var report = await response.Content.ReadAsJsonAsync<UIHealthReport>(); //ymmv as far as json options goes here
report.Should().NotBeNull().And.Match<UIHealthReport>(_ => _.Status == UIHealthStatus.Healthy);
}
} So the downside obviously is you're setting a test flag and checking for it in production code, but it does seem to work for overriding the config with minimal APIs until a real fix comes along in .NET 8. |
omg thank you so much! var builder = WebApplication.CreateBuilder(args); |
Some ppl seem to imply/hope this is getting fixed in .NET 8. Can anyone confirm this is actually getting fixed in .NET 8? This is not just a gotcha but a breaking change in behavior between the old startup and the new WebApplication way of doing things. All the workarounds posted here seem to be pretty hacky and not a true solution to the problem. |
It was not fixed in .NET 8. |
OMG! 🤯 I spent nearly 2 days going through every possible permutation without success. This actually makes the WAF config work on dotnet 6! I had already resigned to using David's temporary workaround until I tried this. Here's my case: I couldn't get AddUserSecrets to load anything, which was problematic since it contains our AWS configuration settings.
|
I want to add my experience in case anyone finds it useful: i'm using .net 8 with testcontainers an therefore need to add my configurations, i thought i should clear the sources before adding news ones but i was wrong, just removing the line fixed the issue for my use case. protected override IHost CreateHost(IHostBuilder builder)
{
builder.ConfigureHostConfiguration(config =>
{
//config.Sources.Clear(); <-- removed this line right here
var configuration = new ConfigurationBuilder()
.AddUserSecrets(typeof(CustomWebApplicationFactory<TProgram>).Assembly, true, true)
.Build();
config.AddConfiguration(configuration);
config.Sources.Add(new MemoryConfigurationSource
{
InitialData = new Dictionary<string, string>
{
{"ConnectionStrings:Database", _database.GetConnectionString().Replace("Database", "Initial Catalog")},
{"ConnectionStrings:Storage", _storage.GetConnectionString()},
{"ConnectionStrings:Redis:Host", _redis.Hostname},
{"ConnectionStrings:Redis:Password", string.Empty}
}!
});
});
return base.CreateHost(builder);
} |
@davidfowl This was not mentioned in any Asp.Net Core 9 "what we are doing" posts. I'm assuming that's mostly because this is a small change/bug fix? This is still in the plans, right? I'm asking since it's still in the |
Is it still planned for dotnet 9? |
I'm fairly sure at this point it will have missed the boat for inclusion in .NET 9 given no one appears to have done any work on it at all. |
Just dropping into say this worked for me. |
Just use old school Startup and Program classes works for me |
I tried all the workaround but setting configuration at the host level create empty section value for each section. class TestServer : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseSetting("mysection:myvalue", "test");
}
} then, in the app var builder = WebApplication.CreateBuilder(args);
bool isNull = builder.Configuration.GetSection("mysection").Value is null will return false. The section has an empty value because the args passed to the application are
How can we get rid of this empty value ? It causes issues with Azure SDK (Azure/azure-sdk-for-net#48368) |
Maybe I don't know something (e.g. I should not call some functions in // Program.cs
var builder = WebApplication.CreateBuilder(args);
var result = builder.Configuration["Result"];
var app = builder.Build();
app.MapGet("/", () => result);
app.Run();
public partial class Program { } // UnitTest1.cs
public class WebAppFactory: WebApplicationFactory<Program>
{
protected override IHost CreateHost(IHostBuilder builder)
{
// note, we are NOT calling builder.ConfigureAppConfiguration(...)
builder.ConfigureHostConfiguration(builder =>
{
builder.AddInMemoryCollection(new Dictionary<string, string?>
{
["Result"] = "Hello World!"
});
});
return base.CreateHost(builder);
}
}
public class UnitTest1
{
[Fact]
public async Task Test1()
{
var waf = new WebAppFactory();
var client = waf.CreateClient();
var response = await client.GetStringAsync("/");
Assert.Equal("Hello World!", response);
}
} |
BTW: one question: Having code like that (explicit controllers) [ApiController]
public class MyController : ControllerBase
{
[HttpGet("hello")]
public IActionResult Hello()
{
return Ok("Hello, World2!");
}
}
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
}
} I've noticed that if in test I do that: public class WebAppFactory : WebApplicationFactory<Program>
{
protected override IHost CreateHost(IHostBuilder builder)
{
builder.ConfigureHostConfiguration(builder =>
{
//builder.Sources.Clear(); // <-- this breaks controllers
});
return base.CreateHost(builder);
}
}
public class UnitTest1
{
[Fact]
public async Task Test1()
{
var waf = new WebAppFactory();
var client = waf.CreateClient();
var response = await client.GetStringAsync("/hello");
Assert.Equal("Hello, World2!", response);
}
} then if I uncomment the |
Overriding |
I've been trying to figure out a way to make this work off and on for months now, as I am eager to get started with .NET Aspire and many of the helper functions in Aspire utilize the |
Removing this feature from the .NET 10 milestone due to resource constraints. |
Describe the bug
Using .net 6 minimal APIs and WebApplicationFactory for testing, the test config override is only applied after .Build.
This means any interaction with config while configuring services using
builder.Configuration
will be using the app configuration, not test overrides.To Reproduce
I've created a repo that shows the difference, here is the Program, and an Example test.
Test run
Further technical details
The text was updated successfully, but these errors were encountered: