Skip to content

Commit dff58d7

Browse files
fix(tests): make WebApplicationFactory service overrides idempotent
Remove prior matching registrations for DbContext options, DbConnection, DatabaseMigrationService, and EnsureCreatedHostedService before re-adding test overrides. Also keep the SQLite keep-alive connection creation guarded with ??= across repeated ConfigureServices invocations. This avoids duplicate EnsureCreated hosted services racing each other and failing CI with SQLite Error 1: table AspNetRoles already exists.
1 parent 395b9d8 commit dff58d7

1 file changed

Lines changed: 33 additions & 19 deletions

File tree

EssentialCSharp.Web.Tests/WebApplicationFactory.cs

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,38 +26,42 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
2626
{
2727
builder.ConfigureServices(services =>
2828
{
29-
ServiceDescriptor? dbContextDescriptor = services.SingleOrDefault(
30-
d => d.ServiceType ==
31-
typeof(IDbContextOptionsConfiguration<EssentialCSharpWebContext>));
32-
33-
if (dbContextDescriptor != null)
29+
for (int i = services.Count - 1; i >= 0; i--)
3430
{
35-
services.Remove(dbContextDescriptor);
31+
if (services[i].ServiceType ==
32+
typeof(IDbContextOptionsConfiguration<EssentialCSharpWebContext>))
33+
{
34+
services.RemoveAt(i);
35+
}
3636
}
3737

38-
ServiceDescriptor? dbConnectionDescriptor =
39-
services.SingleOrDefault(
40-
d => d.ServiceType ==
41-
typeof(DbConnection));
42-
43-
if (dbConnectionDescriptor != null)
38+
for (int i = services.Count - 1; i >= 0; i--)
4439
{
45-
services.Remove(dbConnectionDescriptor);
40+
if (services[i].ServiceType == typeof(DbConnection))
41+
{
42+
services.RemoveAt(i);
43+
}
4644
}
4745

4846
// Remove DatabaseMigrationService: it calls MigrateAsync which conflicts
4947
// with EnsureCreated() used below for the in-memory SQLite test database.
50-
ServiceDescriptor? migrationServiceDescriptor = services.SingleOrDefault(
51-
d => d.ImplementationType == typeof(DatabaseMigrationService));
52-
if (migrationServiceDescriptor != null)
48+
for (int i = services.Count - 1; i >= 0; i--)
5349
{
54-
services.Remove(migrationServiceDescriptor);
50+
ServiceDescriptor descriptor = services[i];
51+
if (descriptor.ServiceType == typeof(IHostedService) &&
52+
descriptor.ImplementationType == typeof(DatabaseMigrationService))
53+
{
54+
services.RemoveAt(i);
55+
}
5556
}
5657

5758
// Open a keep-alive connection to prevent the shared-cache in-memory database from
5859
// being dropped when per-scope connections are disposed between requests.
59-
_keepAliveConnection = new SqliteConnection(_sqlConnectionString);
60-
_keepAliveConnection.Open();
60+
_keepAliveConnection ??= new SqliteConnection(_sqlConnectionString);
61+
if (_keepAliveConnection.State != System.Data.ConnectionState.Open)
62+
{
63+
_keepAliveConnection.Open();
64+
}
6165

6266
// Register as scoped so each request scope gets its own SqliteConnection,
6367
// preventing "database is locked" errors under concurrent requests.
@@ -75,6 +79,16 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
7579
});
7680

7781
// Ensure schema exists before any hosted service that reads from the database.
82+
for (int i = services.Count - 1; i >= 0; i--)
83+
{
84+
ServiceDescriptor descriptor = services[i];
85+
if (descriptor.ServiceType == typeof(IHostedService) &&
86+
descriptor.ImplementationType == typeof(EnsureCreatedHostedService))
87+
{
88+
services.RemoveAt(i);
89+
}
90+
}
91+
7892
ServiceDescriptor ensureCreatedDescriptor =
7993
ServiceDescriptor.Singleton<IHostedService, EnsureCreatedHostedService>();
8094
int firstHostedServiceIndex = -1;

0 commit comments

Comments
 (0)