I have the following code:

public async Task<Result<Guid>> HandleClient<T>(T client) where T : Client
{
    var exists = await _dbContext.Clients.AnyAsync(c => c.ClientId == client.ClientId);
    
    if(exists)
    {
        return Result.Failure<Guid>(new ConflictError(nameof(Client.ClientId), client.ClientId));
    }

    try
    {
        _dbContext.Clients.Add(client);
        await _dbContext.SaveChangesAsync();
        return Result.Success(client.Id);
    }
    catch(Exception e)
    {
        return Result.Failure<Guid>(
            new ExceptionError($"Error when trying to create client '{client.ClientId}'", e));
    }
}

I already have the following integration test that covers the "exists" case:

[Fact]
public async Task ShouldReturn409ConflictWhenClientIdIsNotUnique()
{
    var clientId = Guid.CreateVersion7().ToString();
    var existingClient = new PublicClientBuilder { ClientId = clientId }.Build();
    await using var arrangeScope = _fixture.Services.CreateAsyncScope();
    await using var arrangeDbContext = arrangeScope.ServiceProvider.GetRequiredService<MyDbContext>();
    arrangeDbContext.Clients.Add(existingClient);
    await arrangeDbContext.SaveChangesAsync(_ct);
    var requestBody = new CreatePublicClientRequestBodyBuilder { ClientId = clientId }.Build();
    var client = _fixture.CreateClient();
    var request = new HttpRequestMessage(HttpMethod.Post, "clients") { Content = new JsonContent(requestBody) };

    var response = await client.SendAsync(request, _ct);

    response.StatusCode.ShouldBe(HttpStatusCode.Conflict);
    var problemDetails = await response.ReadProblemDetails(_ct);
    problemDetails.Title.ShouldBe("Conflict");
    problemDetails.Detail.ShouldBe($"'{clientId}' already exists (ClientId)");
}

Now, I also want to test that my code catches exceptions and returns a Result.

My _fixture looks like this:

public class WebFixture : WebApplicationFactory<Program>, IAsyncLifetime
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder
            .UseEnvironment("IntegrationTest")
            .ConfigureAppConfiguration(configurationBuilder =>
            {
                configurationBuilder.Sources.Clear();
                configurationBuilder.AddInMemoryCollection(TestConfiguration!);
                configurationBuilder.AddEnvironmentVariables("TEST_");
            });
    }

    public async ValueTask InitializeAsync()
    {
        await using var scope = Services.CreateAsyncScope();
        await using var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();
        await dbContext.Database.MigrateAsync();
    }
}

I can then resolve MyDbContext like this in my tests:

await using var arrangeScope = _fixture.Services.CreateAsyncScope();
await using var arrangeDbContext = arrangeScope.ServiceProvider.GetRequiredService<MyDbContext>();

It's then possible to create a HttpClient like this and call my endpoint:

var client = _fixture.CreateClient();

Now I only needed a way to be able to configure MyDbContext to throw an exception when SaveChangesAsync is called.

The registration of MyDbContext looks like this:

public static void AddDatabase(this IServiceCollection services, IConfiguration configuration)
{
       services.Configure<PostgresDatabaseOptions>(configuration.GetSection("Storage:Postgres"));
services.AddSingleton<PostgresDatabaseOptions>(x =>
    x.GetRequiredService<IOptions<PostgresDatabaseOptions>>().Value);
services.AddSingleton<NpgsqlDataSource>(provider =>
{
    var postgresOptions = provider.GetRequiredService<PostgresDatabaseOptions>();
    var builder = new NpgsqlConnectionStringBuilder(postgresOptions.ConnectionString)
    {
        Pooling = true,
        MinPoolSize = 2,
        MaxPoolSize = 50
    };
    return NpgsqlDataSource.Create(builder.ConnectionString);
});
services.AddDbContext<MyDbContext, PostgresDbContext>((provider, options) =>
{
    var dataSource = provider.GetRequiredService<NpgsqlDataSource>();
    var interceptors = provider.GetServices<IInterceptor>();
    options.UseNpgsql(dataSource).UseSnakeCaseNamingConvention().AddInterceptors(interceptors.ToArray());
});
}

As you can see, I'm using Postgres and then I configure MyDbContext. When configuring MyDbContext, I also resolve all IInterceptor implementations from the container and ensure it's used.

var interceptors = provider.GetServices<IInterceptor>();
options.UseNpgsql(dataSource).UseSnakeCaseNamingConvention().AddInterceptors(interceptors.ToArray());

I've created the following interceptor in my test project:

public class ThrowingInterceptor : SaveChangesInterceptor
{
    public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
        DbContextEventData eventData,
        InterceptionResult<int> result,
        CancellationToken cancellationToken = default)
    {
        throw new Exception("Boom from test");
    }
}

Now I only need a way to register my interceptor from my test to make sure that it's used.

I've created the following method in my WebFixture:

ublic HttpClient CreateClient(Action<IServiceCollection>? configureServices)
{
    if(configureServices is null)
    {
        return CreateClient();
    }

    return WithWebHostBuilder(builder =>
    {
        builder.ConfigureServices(configureServices);
    }).CreateClient();
}

Since it takes an Action<IServiceCollection>, it's possible to configure the IServiceCollection from the test like this:

var client = _fixture.CreateClient(services =>
{
    services.AddScoped<IInterceptor, ThrowingInterceptor>();
});

The following test will now pass:

[Fact]
    public async Task ShouldReturn500InternalServerErrorWhenExceptionHappens()
{
    var clientId = Guid.CreateVersion7().ToString();
    await using var arrangeScope = _fixture.Services.CreateAsyncScope();
    await using var arrangeDbContext = arrangeScope.ServiceProvider.GetRequiredService<AwthDbContext>();
    var requestBody = new CreatePublicClientRequestBodyBuilder { ClientId = clientId }.Build();
    var request = new HttpRequestMessage(HttpMethod.Post, "clients") { Content = new JsonContent(requestBody) };
    var client = _fixture.CreateClient(services =>
    {
        services.AddScoped<IInterceptor, ThrowingInterceptor>();
    });

    var response = await client.SendAsync(request, _ct);

    response.StatusCode.ShouldBe(HttpStatusCode.InternalServerError);
    var problemDetails = await response.ReadProblemDetails(_ct);
    // Generic response since my API should not leak exception messages
    problemDetails.Title.ShouldBe("An error occurred while processing your request.");
}