Skip to content

Commit 74624fb

Browse files
authored
Merge pull request #1017 from ardalis/kyle/fix-test-container-image
Fix Build for Tests and Update Image Version
2 parents ed5dd0e + 8c4dad2 commit 74624fb

1 file changed

Lines changed: 127 additions & 127 deletions

File tree

Lines changed: 127 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,127 +1,127 @@
1-
using Clean.Architecture.Infrastructure.Data;
2-
using Microsoft.EntityFrameworkCore;
3-
using Microsoft.Extensions.Configuration;
4-
using Testcontainers.MsSql;
5-
6-
namespace Clean.Architecture.FunctionalTests;
7-
8-
public class CustomWebApplicationFactory<TProgram> : WebApplicationFactory<TProgram>, IAsyncLifetime where TProgram : class
9-
{
10-
private MsSqlContainer? _dbContainer;
11-
12-
public async ValueTask InitializeAsync()
13-
{
14-
try
15-
{
16-
_dbContainer = new MsSqlBuilder("mcr.microsoft.com/mssql/server:2022-latest")
17-
.WithPassword("Your_password123!")
18-
.Build();
19-
await _dbContainer.StartAsync();
20-
}
21-
catch (ArgumentException)
22-
{
23-
// Docker is not available; fall back to SQLite (configured via appsettings.Testing.json)
24-
_dbContainer = null;
25-
}
26-
}
27-
28-
public new async ValueTask DisposeAsync()
29-
{
30-
// Clean up environment variable
31-
Environment.SetEnvironmentVariable("USE_SQL_SERVER", null);
32-
if (_dbContainer != null)
33-
{
34-
await _dbContainer.DisposeAsync();
35-
}
36-
}
37-
38-
/// <summary>
39-
/// Overriding CreateHost to avoid creating a separate ServiceProvider per this thread:
40-
/// https://github.com/dotnet-architecture/eShopOnWeb/issues/465
41-
/// </summary>
42-
/// <param name="builder"></param>
43-
/// <returns></returns>
44-
protected override IHost CreateHost(IHostBuilder builder)
45-
{
46-
builder.UseEnvironment("Testing"); // will not send real emails
47-
var host = builder.Build();
48-
host.Start();
49-
50-
// Get service provider.
51-
var serviceProvider = host.Services;
52-
53-
// Create a scope to obtain a reference to the database
54-
// context (AppDbContext).
55-
using (var scope = serviceProvider.CreateScope())
56-
{
57-
var scopedServices = scope.ServiceProvider;
58-
var db = scopedServices.GetRequiredService<AppDbContext>();
59-
60-
var logger = scopedServices
61-
.GetRequiredService<ILogger<CustomWebApplicationFactory<TProgram>>>();
62-
63-
try
64-
{
65-
// Functional tests use EnsureCreated to avoid migration-script coupling.
66-
db.Database.EnsureCreated();
67-
68-
// Seed the database with test data only if it has not been seeded yet.
69-
// This is safe for container reuse across test runs and multiple fixture instances.
70-
SeedData.InitializeAsync(db).GetAwaiter().GetResult();
71-
}
72-
catch (Exception ex)
73-
{
74-
logger.LogError(ex, "An error occurred seeding the database with test messages. Error: {exceptionMessage}", ex.Message);
75-
throw;
76-
}
77-
}
78-
79-
return host;
80-
}
81-
82-
protected override void ConfigureWebHost(IWebHostBuilder builder)
83-
{
84-
if (_dbContainer != null)
85-
{
86-
// Force SQL Server mode even on non-Windows platforms for functional tests
87-
Environment.SetEnvironmentVariable("USE_SQL_SERVER", "true");
88-
}
89-
90-
builder
91-
.ConfigureAppConfiguration((context, config) =>
92-
{
93-
if (_dbContainer != null)
94-
{
95-
// Set the connection string to use the Testcontainer
96-
config.AddInMemoryCollection(new Dictionary<string, string?>
97-
{
98-
["ConnectionStrings:DefaultConnection"] = _dbContainer.GetConnectionString()
99-
});
100-
}
101-
})
102-
.ConfigureServices(services =>
103-
{
104-
if (_dbContainer != null)
105-
{
106-
// Remove the app's ApplicationDbContext registration
107-
var descriptors = services.Where(
108-
d => d.ServiceType == typeof(AppDbContext) ||
109-
d.ServiceType == typeof(DbContextOptions<AppDbContext>))
110-
.ToList();
111-
112-
foreach (var descriptor in descriptors)
113-
{
114-
services.Remove(descriptor);
115-
}
116-
117-
// Add ApplicationDbContext using the Testcontainers SQL Server instance
118-
services.AddDbContext<AppDbContext>((provider, options) =>
119-
{
120-
options.UseSqlServer(_dbContainer.GetConnectionString());
121-
var interceptor = provider.GetRequiredService<EventDispatchInterceptor>();
122-
options.AddInterceptors(interceptor);
123-
});
124-
}
125-
});
126-
}
127-
}
1+
using Clean.Architecture.Infrastructure.Data;
2+
using Microsoft.EntityFrameworkCore;
3+
using Microsoft.Extensions.Configuration;
4+
using Testcontainers.MsSql;
5+
6+
namespace Clean.Architecture.FunctionalTests;
7+
8+
public class CustomWebApplicationFactory<TProgram> : WebApplicationFactory<TProgram>, IAsyncLifetime where TProgram : class
9+
{
10+
private MsSqlContainer? _dbContainer;
11+
12+
public async ValueTask InitializeAsync()
13+
{
14+
try
15+
{
16+
_dbContainer = new MsSqlBuilder("mcr.microsoft.com/mssql/server:2025-latest")
17+
.WithPassword("Your_password123!")
18+
.Build();
19+
await _dbContainer.StartAsync();
20+
}
21+
catch (ArgumentException)
22+
{
23+
// Docker is not available; fall back to SQLite (configured via appsettings.Testing.json)
24+
_dbContainer = null;
25+
}
26+
}
27+
28+
public new async ValueTask DisposeAsync()
29+
{
30+
// Clean up environment variable
31+
Environment.SetEnvironmentVariable("USE_SQL_SERVER", null);
32+
if (_dbContainer != null)
33+
{
34+
await _dbContainer.DisposeAsync();
35+
}
36+
}
37+
38+
/// <summary>
39+
/// Overriding CreateHost to avoid creating a separate ServiceProvider per this thread:
40+
/// https://github.com/dotnet-architecture/eShopOnWeb/issues/465
41+
/// </summary>
42+
/// <param name="builder"></param>
43+
/// <returns></returns>
44+
protected override IHost CreateHost(IHostBuilder builder)
45+
{
46+
builder.UseEnvironment("Testing"); // will not send real emails
47+
var host = builder.Build();
48+
host.Start();
49+
50+
// Get service provider.
51+
var serviceProvider = host.Services;
52+
53+
// Create a scope to obtain a reference to the database
54+
// context (AppDbContext).
55+
using (var scope = serviceProvider.CreateScope())
56+
{
57+
var scopedServices = scope.ServiceProvider;
58+
var db = scopedServices.GetRequiredService<AppDbContext>();
59+
60+
var logger = scopedServices
61+
.GetRequiredService<ILogger<CustomWebApplicationFactory<TProgram>>>();
62+
63+
try
64+
{
65+
// Functional tests use EnsureCreated to avoid migration-script coupling.
66+
db.Database.EnsureCreated();
67+
68+
// Seed the database with test data only if it has not been seeded yet.
69+
// This is safe for container reuse across test runs and multiple fixture instances.
70+
SeedData.InitializeAsync(db).GetAwaiter().GetResult();
71+
}
72+
catch (Exception ex)
73+
{
74+
logger.LogError(ex, "An error occurred seeding the database with test messages. Error: {exceptionMessage}", ex.Message);
75+
throw;
76+
}
77+
}
78+
79+
return host;
80+
}
81+
82+
protected override void ConfigureWebHost(IWebHostBuilder builder)
83+
{
84+
if (_dbContainer != null)
85+
{
86+
// Force SQL Server mode even on non-Windows platforms for functional tests
87+
Environment.SetEnvironmentVariable("USE_SQL_SERVER", "true");
88+
}
89+
90+
builder
91+
.ConfigureAppConfiguration((context, config) =>
92+
{
93+
if (_dbContainer != null)
94+
{
95+
// Set the connection string to use the Testcontainer
96+
config.AddInMemoryCollection(new Dictionary<string, string?>
97+
{
98+
["ConnectionStrings:DefaultConnection"] = _dbContainer.GetConnectionString()
99+
});
100+
}
101+
})
102+
.ConfigureServices(services =>
103+
{
104+
if (_dbContainer != null)
105+
{
106+
// Remove the app's ApplicationDbContext registration
107+
var descriptors = services.Where(
108+
d => d.ServiceType == typeof(AppDbContext) ||
109+
d.ServiceType == typeof(DbContextOptions<AppDbContext>))
110+
.ToList();
111+
112+
foreach (var descriptor in descriptors)
113+
{
114+
services.Remove(descriptor);
115+
}
116+
117+
// Add ApplicationDbContext using the Testcontainers SQL Server instance
118+
services.AddDbContext<AppDbContext>((provider, options) =>
119+
{
120+
options.UseSqlServer(_dbContainer.GetConnectionString());
121+
var interceptor = provider.GetRequiredService<EventDispatchInterceptor>();
122+
options.AddInterceptors(interceptor);
123+
});
124+
}
125+
});
126+
}
127+
}

0 commit comments

Comments
 (0)