如何确保每个 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 社区能提出更优雅的解决方案...
我正在使用 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 社区能提出更优雅的解决方案...