-
Hi Thom, public sealed class DependencyInjectionClassConstructor : IClassConstructor, ITestEndEventReceiver, ILastTestInAssemblyEventReceiver
{
internal static TestWebApplicationFactory WebApplication { get; } = CreateWebApplication().GetAwaiter().GetResult();
private AsyncServiceScope _scope;
public T Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(
ClassConstructorMetadata classConstructorMetadata
)
where T : class
{
_scope = WebApplication.Services.CreateAsyncScope();
var httpContextAccessor = WebApplication.Services.GetRequiredService<IHttpContextAccessor>();
httpContextAccessor.HttpContext = new DefaultHttpContext
{
RequestServices = _scope.ServiceProvider
};
return ActivatorUtilities.GetServiceOrCreateInstance<T>(_scope.ServiceProvider);
}
public async ValueTask OnTestEnd(TestContext testContext)
{
var httpContextAccessor = _scope.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
var a = httpContextAccessor.HttpContext;
await _scope.DisposeAsync();
}
public async ValueTask IfLastTestInAssembly(AssemblyHookContext context, TestContext testContext)
{
await WebApplication.DisposeAsync();
}
private static async Task<TestWebApplicationFactory> CreateWebApplication()
{
var testContainer = new PostgreSqlBuilder().Build();
await testContainer.StartAsync();
return new TestWebApplicationFactory(testContainer.GetConnectionString());
}
[Before(Assembly)]
public static async Task BeforeAssembly()
{
await MigrateApplication();
await IntegrationTestContext.SetupTenant();
}
private static async Task MigrateApplication()
{
await using var serviceScope = WebApplication.Services.CreateAsyncScope();
await using var dbContext = serviceScope.ServiceProvider.GetRequiredService<AppDbContext>();
_ = await dbContext.Database.EnsureCreatedAsync();
await dbContext.Database.MigrateAsync();
}
} In the Now the issue lies in when these tests run in parallel, I understand that having the static WebApplication field shares this state between test cases, and this is what I want, but I was wondering what my options are for having the correct HttpContext with the correct test case. Two options I've thought of:
Looking forward to any ideas you might have. |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 4 replies
-
So IHttpContextAccessor stores the contexts in an AsyncLocal field. You're currently setting the context in the create method, which isn't in the tests async context yet, because it's needed earlier on the build the test. Could you try subscribing to a test start event, and setting the context there? That method should have the tests async context so hopefully would set the async local properly. |
Beta Was this translation helpful? Give feedback.
-
For any other people looking to do the same thing, you have 2 options: Option 1: Create a custom implementation of public ValueTask OnTestStart(BeforeTestContext beforeTestContext)
{
var httpContextAccessor = _scope.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
httpContextAccessor.HttpContext ??= new DefaultHttpContext
{
RequestServices = _scope.ServiceProvider
};
return ValueTask.CompletedTask;
} This sets the HttpContext for the current scope with the correctly scoped services for the current test Option 2: Use the default [ClassConstructor<DependencyInjectionClassConstructor>]
internal sealed class TestClass1(
IHttpContextAccessor httpContextAccessor,
IServiceProvider services
)
{
[Test]
public async Task Test1()
{
httpContextAccessor.HttpContext ??= new DefaultHttpContext { RequestServices = services };
}
} Please feel free to comment if you've found any other (better) ways to do it. |
Beta Was this translation helpful? Give feedback.
-
I've just made some changes in 0.4.0 so this should work: public sealed class DependencyInjectionClassConstructor : IClassConstructor, ITestStartEventReceiver, ITestEndEventReceiver, ILastTestInAssemblyEventReceiver
{
internal static TestWebApplicationFactory WebApplication { get; } = CreateWebApplication().GetAwaiter().GetResult();
private AsyncServiceScope _scope;
public T Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(
ClassConstructorMetadata classConstructorMetadata
)
where T : class
{
_scope = WebApplication.Services.CreateAsyncScope();
return ActivatorUtilities.GetServiceOrCreateInstance<T>(_scope.ServiceProvider);
}
public async ValueTask OnTestStart(BeforeTestContext testContext)
{
return Task.CompletedTask;
}
// Limitation currently with AsyncLocal - We have to set it in a synchronous method for it to propogate properly
public void OnTestStartSynchronous(BeforeTestContext testContext)
{
var httpContextAccessor = WebApplication.Services.GetRequiredService<IHttpContextAccessor>();
httpContextAccessor.HttpContext = new DefaultHttpContext
{
RequestServices = _scope.ServiceProvider
};
}
public async ValueTask OnTestEnd(TestContext testContext)
{
var httpContextAccessor = _scope.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
var a = httpContextAccessor.HttpContext;
await _scope.DisposeAsync();
}
public async ValueTask IfLastTestInAssembly(AssemblyHookContext context, TestContext testContext)
{
await WebApplication.DisposeAsync();
}
private static async Task<TestWebApplicationFactory> CreateWebApplication()
{
var testContainer = new PostgreSqlBuilder().Build();
await testContainer.StartAsync();
return new TestWebApplicationFactory(testContainer.GetConnectionString());
}
[Before(Assembly)]
public static async Task BeforeAssembly()
{
await MigrateApplication();
await IntegrationTestContext.SetupTenant();
}
private static async Task MigrateApplication()
{
await using var serviceScope = WebApplication.Services.CreateAsyncScope();
await using var dbContext = serviceScope.ServiceProvider.GetRequiredService<AppDbContext>();
_ = await dbContext.Database.EnsureCreatedAsync();
await dbContext.Database.MigrateAsync();
}
} You should be able to do this with the default HttpContextAccessor, and not need to register your own scoped version. |
Beta Was this translation helpful? Give feedback.
For any other people looking to do the same thing, you have 2 options:
Option 1: Create a custom implementation of
IHttpContextAccessor
and register it as scoped in yourWebApplicationFactory<T>
, then in yourDependencyInjectionClassConstructor
, inherit fromITestStartEventReceiver
and add the following codeThis sets the HttpContext for the current scope with the correctly sco…