如何确保每个 xunit 测试都有一个新的 InMemory 种子数据库?

How do I ensure a new InMemory seeded Database for each xunit test?

我正在使用 xunit 为我的 Asp.Net 核心 API 设置集成测试。到目前为止,我有两个测试设置:

public class BasicTests : IClassFixture<CustomWebApplicationFactory<EcommerceWebAPI.Startup>>
{
    private readonly CustomWebApplicationFactory<EcommerceWebAPI.Startup> _factory;

    public BasicTests(CustomWebApplicationFactory<EcommerceWebAPI.Startup> factory)
    {
        _factory = factory;
    }

    [Theory]
    [InlineData("84.247.85.224")]
    public async Task AuthenticateUserTest(string ip)
    {
        // Arrange
        var client = _factory.CreateClient();
        int responseStatusCode = 0;

        // Act
        var request = new HttpRequestMessage(HttpMethod.Post, "/api/Users/AuthenticateUser")
        {
            Content = new StringContent("{\"Username\":\"test@test.com\",\"Password\":\"test\"}", Encoding.UTF8,
                "application/json")
        };
        request.Headers.Add("X-Real-IP", ip);

        var response = await client.SendAsync(request);
        responseStatusCode = (int)response.StatusCode;

        // Assert
        Assert.Equal(200, responseStatusCode);
    }

    [Theory]
    [InlineData("84.247.85.224")]
    [InlineData("84.247.85.225")]
    [InlineData("84.247.85.226:6555")]
    [InlineData("205.156.136.211, 192.168.29.47:54610")]
    public async Task SpecificIpRule(string ip)
    {
        // Arrange
        var client = _factory.CreateClient();
        int responseStatusCode = 0;

        // Act
        for (int i = 0; i < 4; i++)
        {
            var request = new HttpRequestMessage(HttpMethod.Post, "/api/Users/AuthenticateUser")
            {
                Content = new StringContent("{\"Username\":\"Test\",\"Password\":\"Test\"}", Encoding.UTF8,
                    "application/json")
            };
            request.Headers.Add("X-Real-IP", ip);

            var response = await client.SendAsync(request);
            responseStatusCode = (int)response.StatusCode;
        }

        // Assert
        Assert.Equal(429, responseStatusCode);
    }
}

我的自定义应用工厂:

public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            var descriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                     typeof(DbContextOptions<EntityContext>));

            services.Remove(descriptor);

            services.AddDbContext<EntityContext>(options =>
            {
                options.UseInMemoryDatabase("InMemoryDbForTesting");
            });

            var sp = services.BuildServiceProvider();

            using (var scope = sp.CreateScope())
            {
                var scopedServices = scope.ServiceProvider;
                var db = scopedServices.GetRequiredService<EntityContext>();
                var logger = scopedServices
                    .GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();

                db.Database.EnsureDeleted();

                db.Database.EnsureCreated();

                try
                {
                    Utilities.InitializeDbForTests(db);
                }
                catch (Exception ex)
                {
                    logger.LogError(ex, "An error occurred seeding the " +
                                        "database with test messages. Error: {Message}", ex.Message);
                }
            }
        });
    }
}

我为数据库播种:

static class Utilities
{
    public static void InitializeDbForTests(EntityContext db)
    {
        var customer = new Customer
        {
            Custnmbr = "AARONFIT0001"
        };

        db.Customers.Add(customer);
        
        var user = new User
        {
            Id = 1,
            Customer = customer,
            EmailAddress = "test@test.com",
            PasswordHash = BCrypt.Net.BCrypt.HashPassword("test")
        };

        db.Users.Add(user);
        db.SaveChanges();
    }
}

两个测试用例都通过了。但是,当第二次测试再次尝试 运行 种子时,我在日志中得到一个实体已经存在的异常。我认为将行 db.Database.EnsureDeleted() 放在我的 CustomWebApplicationFactory 中将确保每个测试都从一个全新的 InMemory 数据库开始。如何确保每次测试都有一个新的 InMemory 种子数据库?

经过一些额外的搜索,一篇特定的 Whosebug 文章 Resetting In-Memory database between integration tests 让我得出了这个结论。我需要为每个测试初始化​​工厂...

public class AuthenticationTests
{
    [Theory]
    [InlineData("84.247.85.224")]
    public async Task AuthenticateUserTest(string ip)
    {
        // Arrange
        using var factory = new CustomWebApplicationFactory<EcommerceWebAPI.Startup>();
        var client = factory.CreateClient();
        int responseStatusCode = 0;

        // Act
        var request = new HttpRequestMessage(HttpMethod.Post, "/api/Users/AuthenticateUser")
        {
            Content = new StringContent("{\"Username\":\"test@test.com\",\"Password\":\"test\"}", Encoding.UTF8,
                "application/json")
        };
        request.Headers.Add("X-Real-IP", ip);

        var response = await client.SendAsync(request);
        responseStatusCode = (int)response.StatusCode;

        // Assert
        Assert.Equal(200, responseStatusCode);
    }

    [Theory]
    [InlineData("84.247.85.224")]
    [InlineData("84.247.85.225")]
    [InlineData("84.247.85.226:6555")]
    [InlineData("205.156.136.211, 192.168.29.47:54610")]
    public async Task SpecificIpRule(string ip)
    {
        // Arrange
        using var factory = new CustomWebApplicationFactory<EcommerceWebAPI.Startup>();
        var client = factory.CreateClient();
        int responseStatusCode = 0;

        // Act
        for (int i = 0; i < 4; i++)
        {
            var request = new HttpRequestMessage(HttpMethod.Post, "/api/Users/AuthenticateUser")
            {
                Content = new StringContent("{\"Username\":\"Test\",\"Password\":\"Test\"}", Encoding.UTF8,
                    "application/json")
            };
            request.Headers.Add("X-Real-IP", ip);

            var response = await client.SendAsync(request);
            responseStatusCode = (int)response.StatusCode;
        }

        // Assert
        Assert.Equal(429, responseStatusCode);
    }
}

希望 SO 社区能提出更优雅的解决方案...