Db Config options error with Integration Testing project
Integration tests
Today I had a problem by trying to generate an Integration Test project on my Rest API.
I asked some help from Gemini AI and here was the issue, the configuration for the AppDbContext.
What Gemini agent says is:
The issue wasn't that your logic was wrong; it was that
AddDbContextdoes not replace configuration; it stacks it.Here is the deep dive into why the "Standard Way" failed and why the "Bypass Way" worked.
| Feature | The Failing Code (Standard) | The Working Code (The Fix) |
| The Code | services.AddDbContext<AppDbContext>(opts => opts.UseInMemory...); |
|
| Metaphor | You told the chef: "Add these ingredients to the existing recipe." | You told the chef: "Throw away the recipe. Here is a finished cake I brought from home." |
| Internal Behavior |
Since | You manually built the DbContextOptions object immediately. You then injected that Result as a Singleton. |
| The Crash | The Builder received UseSqlServer (from Program.cs) AND UseInMemory (from Test). It panicked because it can't have two providers. | The Builder inside Program.cs was never touched. The app simply used the pre-built options object you provided. |
| Control | Low. You rely on the DI system to merge configurations correctly. | High. You completely bypass the DI configuration logic. |
The error on my code was the next chunk:
The issue wasn't that your logic was wrong; it was that AddDbContext does not replace configuration; it stacks it.
Here is the deep dive into why the "Standard Way" failed and why the "Bypass Way" worked.
The Comparison
| Feature | The Failing Code (Standard) | The Working Code (The Fix) |
| The Code | services.AddDbContext<AppDbContext>(opts => opts.UseInMemory...); |
|
| Metaphor | You told the chef: "Add these ingredients to the existing recipe." | You told the chef: "Throw away the recipe. Here is a finished cake I brought from home." |
| Internal Behavior |
Since | You manually built the DbContextOptions object immediately. You then injected that Result as a Singleton. |
| The Crash | The Builder received UseSqlServer (from Program.cs) AND UseInMemory (from Test). It panicked because it can't have two providers. | The Builder inside Program.cs was never touched. The app simply used the pre-built options object you provided. |
| Control | Low. You rely on the DI system to merge configurations correctly. | High. You completely bypass the DI configuration logic. |
The Mechanics: Why Remove didn't work before
You might be wondering: "But I called services.Remove! Why was the SQL configuration still there?"
This is the most confusing part of ASP.NET Core Dependency Injection.
The Service vs. The Config:
When you call builder.Services.AddDbContext(...) in Program.cs, it actually does two things:
It registers
AppDbContextas a service.It registers an internal generic interface called
IConfigureOptions<DbContextOptions>.
The "Zombie" Config:
In your previous attempts, you removed AppDbContext and DbContextOptions. However, you did not remove the hidden IConfigureOptions that Program.cs created.
The Collision:
When your Test Factory ran services.AddDbContext(opts => UseInMemory...), it added a second IConfigureOptions.
When the app started, the DI container said: "Okay, let's build the options."
It found the
IConfigureOptionsfromProgram.cs$\rightarrow$ Added SQL.It found the
IConfigureOptionsfromFactory$\rightarrow$ Added In-Memory.Result:
InvalidOperationException.
It is great to hear that the tests are finally passing! This was a classic "Dependency Injection Trap."
The issue wasn't that your logic was wrong; it was that AddDbContext does not replace configuration; it stacks it.
Here is the deep dive into why the "Standard Way" failed and why the "Bypass Way" worked.
The Comparison
| Feature | The Failing Code (Standard) | The Working Code (The Fix) |
| The Code | services.AddDbContext<AppDbContext>(opts => opts.UseInMemory...); |
|
| Metaphor | You told the chef: "Add these ingredients to the existing recipe." | You told the chef: "Throw away the recipe. Here is a finished cake I brought from home." |
| Internal Behavior |
Since | You manually built the DbContextOptions object immediately. You then injected that Result as a Singleton. |
| The Crash | The Builder received UseSqlServer (from Program.cs) AND UseInMemory (from Test). It panicked because it can't have two providers. | The Builder inside Program.cs was never touched. The app simply used the pre-built options object you provided. |
| Control | Low. You rely on the DI system to merge configurations correctly. | High. You completely bypass the DI configuration logic. |
The Mechanics: Why Remove didn't work before
You might be wondering: "But I called services.Remove! Why was the SQL configuration still there?"
This is the most confusing part of ASP.NET Core Dependency Injection.
The Service vs. The Config:
When you call builder.Services.AddDbContext(...) in Program.cs, it actually does two things:
It registers
AppDbContextas a service.It registers an internal generic interface called
IConfigureOptions<DbContextOptions>.
The "Zombie" Config:
In your previous attempts, you removed AppDbContext and DbContextOptions. However, you did not remove the hidden IConfigureOptions that Program.cs created.
The Collision:
When your Test Factory ran services.AddDbContext(opts => UseInMemory...), it added a second IConfigureOptions.
When the app started, the DI container said: "Okay, let's build the options."
It found the
IConfigureOptionsfromProgram.cs$\rightarrow$ Added SQL.It found the
IConfigureOptionsfromFactory$\rightarrow$ Added In-Memory.Result:
InvalidOperationException.
Why the Solution Works (The "Bypass")
The solution works because we stopped fighting the configuration system and simply replaced the result.
By doing this:
// 1. Build the object manually (No DI involvement yet)var options = new DbContextOptionsBuilder<AppDbContext>() .UseInMemoryDatabase("InMemoryDbForTesting") .Options;
// 2. Inject the FINISHED objectservices.AddSingleton<DbContextOptions<AppDbContext>>(options);We effectively told the application: "I don't care what configurations or recipes (IConfigureOptions) exist in the system. Whenever anyone asks for DbContextOptions, just hand them this specific object I made."
Because the options are already built, the Program.cs logic (SQL Server) never executes, and the conflict is avoided entirely.
Your Next Step: The "Golden Rule" for Integration Tests
To prevent this in the future, remember this rule for IntegrationTestFactory:
If
Program.csconfigures a specific setting (like DB, Auth, or Logging) using a builder pattern, do not try to re-configure it in the test using the same builder.Instead, build the object manually and inject the result as a Singleton.
Comments
Post a Comment