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 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.

FeatureThe Failing Code (Standard)The Working Code (The Fix)
The Codeservices.AddDbContext<AppDbContext>(opts => opts.UseInMemory...);

var options = new DbContextOptionsBuilder<AppContext>().UseInMemory...;


services.AddSingleton(options.Options);

MetaphorYou 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

AddDbContext registers a Configuration Delegate (a Lambda). It says: "When the context starts, run this function."



Since Program.cs also registered a delegate, BOTH ran.

You manually built the DbContextOptions object immediately. You then injected that Result as a Singleton.
The CrashThe 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.
ControlLow. 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 code that worked was the next:



And I am saving the AI Explanation for further reference:


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

FeatureThe Failing Code (Standard)The Working Code (The Fix)
The Codeservices.AddDbContext<AppDbContext>(opts => opts.UseInMemory...);

var options = new DbContextOptionsBuilder<AppContext>().UseInMemory...;


services.AddSingleton(options.Options);

MetaphorYou 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

AddDbContext registers a Configuration Delegate (a Lambda). It says: "When the context starts, run this function."



Since Program.cs also registered a delegate, BOTH ran.

You manually built the DbContextOptions object immediately. You then injected that Result as a Singleton.
The CrashThe 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.
ControlLow. 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.

  1. The Service vs. The Config:

    When you call builder.Services.AddDbContext(...) in Program.cs, it actually does two things:

    • It registers AppDbContext as a service.

    • It registers an internal generic interface called IConfigureOptions<DbContextOptions>.

  2. The "Zombie" Config:

    In your previous attempts, you removed AppDbContext and DbContextOptions. However, you did not remove the hidden IConfigureOptions that Program.cs created.

  3. 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 IConfigureOptions from Program.cs $\rightarrow$ Added SQL.

    • It found the IConfigureOptions from Factory $\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

FeatureThe Failing Code (Standard)The Working Code (The Fix)
The Codeservices.AddDbContext<AppDbContext>(opts => opts.UseInMemory...);

var options = new DbContextOptionsBuilder<AppContext>().UseInMemory...;


services.AddSingleton(options.Options);

MetaphorYou 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

AddDbContext registers a Configuration Delegate (a Lambda). It says: "When the context starts, run this function."



Since Program.cs also registered a delegate, BOTH ran.

You manually built the DbContextOptions object immediately. You then injected that Result as a Singleton.
The CrashThe 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.
ControlLow. 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.

  1. The Service vs. The Config:

    When you call builder.Services.AddDbContext(...) in Program.cs, it actually does two things:

    • It registers AppDbContext as a service.

    • It registers an internal generic interface called IConfigureOptions<DbContextOptions>.

  2. The "Zombie" Config:

    In your previous attempts, you removed AppDbContext and DbContextOptions. However, you did not remove the hidden IConfigureOptions that Program.cs created.

  3. 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 IConfigureOptions from Program.cs $\rightarrow$ Added SQL.

    • It found the IConfigureOptions from Factory $\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:

C#
// 1. Build the object manually (No DI involvement yet)
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDbForTesting")
.Options;

// 2. Inject the FINISHED object
services.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.cs configures 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

Popular Posts