-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathWebApplicationFactory.cs
More file actions
137 lines (119 loc) · 5.2 KB
/
WebApplicationFactory.cs
File metadata and controls
137 lines (119 loc) · 5.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
using System.Data.Common;
using EssentialCSharp.Web.Data;
using EssentialCSharp.Web.Services;
using TUnit.Core.Interfaces;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.FileProviders;
using Moq.AutoMock;
namespace EssentialCSharp.Web.Tests;
public sealed class WebApplicationFactory : WebApplicationFactory<Program>, IAsyncInitializer
{
public Task InitializeAsync()
{
// Force eager server initialization before tests run.
// This is thread-safe and prevents race conditions from parallel tests
// calling CreateClient() concurrently during lazy init.
_ = Server;
return Task.CompletedTask;
}
private static string SqlConnectionString => $"DataSource=file:{Guid.NewGuid()}?mode=memory&cache=shared";
private SqliteConnection? _Connection;
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
ServiceDescriptor? dbContextDescriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(IDbContextOptionsConfiguration<EssentialCSharpWebContext>));
if (dbContextDescriptor != null)
{
services.Remove(dbContextDescriptor);
}
ServiceDescriptor? dbConnectionDescriptor =
services.SingleOrDefault(
d => d.ServiceType ==
typeof(DbConnection));
if (dbConnectionDescriptor != null)
{
services.Remove(dbConnectionDescriptor);
}
// Remove DatabaseMigrationService: it calls MigrateAsync which conflicts
// with EnsureCreated() used below for the in-memory SQLite test database.
ServiceDescriptor? migrationServiceDescriptor = services.SingleOrDefault(
d => d.ImplementationType == typeof(DatabaseMigrationService));
if (migrationServiceDescriptor != null)
{
services.Remove(migrationServiceDescriptor);
}
_Connection = new SqliteConnection(SqlConnectionString);
_Connection.Open();
services.AddDbContext<EssentialCSharpWebContext>(options =>
{
options.UseSqlite(_Connection);
});
using ServiceProvider serviceProvider = services.BuildServiceProvider();
using IServiceScope scope = serviceProvider.CreateScope();
IServiceProvider scopedServices = scope.ServiceProvider;
EssentialCSharpWebContext db = scopedServices.GetRequiredService<EssentialCSharpWebContext>();
db.Database.EnsureCreated();
// Replace IListingSourceCodeService with one backed by TestData
services.RemoveAll<IListingSourceCodeService>();
string testDataPath = Path.Join(AppContext.BaseDirectory, "TestData");
var fileProvider = new PhysicalFileProvider(testDataPath);
services.AddSingleton<IListingSourceCodeService>(sp =>
{
var mocker = new AutoMocker();
mocker.Setup<IWebHostEnvironment, string>(m => m.ContentRootPath).Returns(testDataPath);
mocker.Setup<IWebHostEnvironment, IFileProvider>(m => m.ContentRootFileProvider).Returns(fileProvider);
return mocker.CreateInstance<ListingSourceCodeService>();
});
});
}
/// <summary>
/// Executes an action within a service scope, handling scope creation and cleanup automatically.
/// </summary>
/// <typeparam name="T">The return type of the action</typeparam>
/// <param name="action">The action to execute with the scoped service provider</param>
/// <returns>The result of the action</returns>
public T InServiceScope<T>(Func<IServiceProvider, T> action)
{
var factory = Services.GetRequiredService<IServiceScopeFactory>();
using var scope = factory.CreateScope();
return action(scope.ServiceProvider);
}
/// <summary>
/// Executes an action within a service scope, handling scope creation and cleanup automatically.
/// </summary>
/// <param name="action">The action to execute with the scoped service provider</param>
public void InServiceScope(Action<IServiceProvider> action)
{
var factory = Services.GetRequiredService<IServiceScopeFactory>();
using var scope = factory.CreateScope();
action(scope.ServiceProvider);
}
public override async ValueTask DisposeAsync()
{
await base.DisposeAsync().ConfigureAwait(false);
if (_Connection != null)
{
await _Connection.DisposeAsync().ConfigureAwait(false);
_Connection = null;
}
GC.SuppressFinalize(this);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_Connection?.Dispose();
_Connection = null;
}
}
}