refactor(tests): TestWebAppFactory.CreateClientWithOverrides para DI override por test (closes #36)
Agrega helper CreateClientWithOverrides en TestWebAppFactory que envuelve WithWebHostBuilder+ConfigureTestServices para inyectar stubs por test sin tocar la fábrica compartida. Usa el patrón para agregar 2 tests e2e: Deactivate_WhenProductQueryReturnsInUse_Returns409WithErrorCode (PRD-001/PRD-002) y CreateRubro_WhenParentHasAvisos_Returns409WithErrorCode (CAT-002). Remueve el comentario TODO PRD-002. 287 Api tests verdes.
This commit is contained in:
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using SIGCM2.Application.Abstractions.Security;
|
||||
using SIGCM2.Infrastructure.Persistence;
|
||||
using SIGCM2.Infrastructure.Security;
|
||||
@@ -14,6 +15,31 @@ namespace SIGCM2.TestSupport;
|
||||
/// <summary>
|
||||
/// WebApplicationFactory for integration tests against SIGCM2.Api.
|
||||
/// Uses SIGCM2_Test_Api database (isolated from Application.Tests which uses SIGCM2_Test_App).
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Per-test DI overrides</b> — use <see cref="CreateClientWithOverrides"/> when a test needs to
|
||||
/// replace a scoped service (e.g. <c>IProductQueryRepository</c>, <c>IAvisoQueryRepository</c>)
|
||||
/// without touching the shared factory:
|
||||
/// </para>
|
||||
/// <code>
|
||||
/// using var client = _factory.CreateClientWithOverrides(services =>
|
||||
/// {
|
||||
/// services.RemoveAll<IMyRepository>();
|
||||
/// services.AddScoped<IMyRepository>(_ => new MyFakeRepository());
|
||||
/// });
|
||||
/// </code>
|
||||
/// <para>
|
||||
/// Internally this calls <see cref="WebApplicationFactory{TEntryPoint}.WithWebHostBuilder"/> which
|
||||
/// creates an independent child host. The RSA key singletons are re-loaded from the same PEM files
|
||||
/// and do not conflict with the parent host — each child host owns its own DI container.
|
||||
/// The child factory (and its host) is disposed when the returned <see cref="HttpClient"/> is disposed.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Why this works safely</b>: <c>WithWebHostBuilder</c> does NOT share the parent host's DI root.
|
||||
/// It re-runs <c>ConfigureWebHost</c> (re-loading RSA keys from disk), then applies the caller's
|
||||
/// <c>ConfigureTestServices</c> on top. The RSA singleton lives in the child's root scope and is
|
||||
/// disposed with that child factory — no cross-factory leakage.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class TestWebAppFactory : WebApplicationFactory<Program>, IAsyncLifetime
|
||||
{
|
||||
@@ -68,6 +94,46 @@ public sealed class TestWebAppFactory : WebApplicationFactory<Program>, IAsyncLi
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="HttpClient"/> against a child host that inherits all base configuration
|
||||
/// but applies the caller's additional <paramref name="overrides"/> on top via
|
||||
/// <c>ConfigureTestServices</c>.
|
||||
///
|
||||
/// <para>
|
||||
/// The returned <see cref="HttpClient"/> is tied to the child factory's lifetime.
|
||||
/// Dispose the client when the test finishes to release the child host:
|
||||
/// <code>using var client = _factory.CreateClientWithOverrides(s => { ... });</code>
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Typical usage — inject a stub repository for a specific test scenario:
|
||||
/// </para>
|
||||
/// <code>
|
||||
/// using var client = _factory.CreateClientWithOverrides(services =>
|
||||
/// {
|
||||
/// services.RemoveAll<IProductQueryRepository>();
|
||||
/// services.AddScoped<IProductQueryRepository>(_ => new AlwaysInUseProductQueryRepository());
|
||||
/// });
|
||||
/// var resp = await client.SendAsync(...);
|
||||
/// Assert.Equal(HttpStatusCode.Conflict, resp.StatusCode);
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="overrides">
|
||||
/// Action applied to <see cref="IServiceCollection"/> after all production services are
|
||||
/// registered. Use <c>services.RemoveAll<T>()</c> then <c>services.AddScoped<T>(...)</c>
|
||||
/// to replace an existing registration.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A new <see cref="HttpClient"/> backed by the child host. The client owns the child factory
|
||||
/// via <see cref="WebApplicationFactory{TEntryPoint}.CreateClient()"/>'s disposal chain.
|
||||
/// </returns>
|
||||
public HttpClient CreateClientWithOverrides(Action<IServiceCollection> overrides)
|
||||
{
|
||||
var child = WithWebHostBuilder(builder =>
|
||||
builder.ConfigureTestServices(overrides));
|
||||
return child.CreateClient();
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
await _dbFixture.InitializeAsync();
|
||||
|
||||
Reference in New Issue
Block a user