如何为每个测试创建单独的内存数据库?
How to create separate in memory database for each test?
我正在 .NET Core 3.0 上使用 xunit 编写测试,内存数据库有问题。我需要为每个测试创建一个单独的数据库,但现在我创建了一个导致问题的数据库,但我不知道如何为每个测试创建一个新数据库。
public class AccountAdminTest : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly HttpClient _client;
private IServiceScopeFactory scopeFactory;
private readonly CustomWebApplicationFactory<Startup> _factory;
private ApplicationDbContext _context;
public AccountAdminTest(CustomWebApplicationFactory<Startup> factory)
{
_factory = factory;
_client = _factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = true,
BaseAddress = new Uri("https://localhost:44444")
});
scopeFactory = _factory.Services.GetService<IServiceScopeFactory>();
var scope = scopeFactory.CreateScope();
_context = scope.ServiceProvider.GetService<ApplicationDbContext>();
}
}
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
var descriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(DbContextOptions<ApplicationDbContext>));
if (descriptor != null)
{
services.Remove(descriptor);
}
services.AddDbContext<ApplicationDbContext>((options, context) =>
{
context.UseInMemoryDatabase("IdentityDatabase");
});
});
}
}
现在看起来像这样,但还是不行。当我在 AddDbContext 上更改生命周期时,它不会改变任何东西。
public class AccountAdminTest : IDisposable
{
public AccountAdminTest(ITestOutputHelper output)
{
this.output = output;
_factory = new CustomWebApplicationFactory<Startup>();
_client = _factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = true,
BaseAddress = new Uri("https://localhost:44444")
});
scopeFactory = _factory.Services.GetService<IServiceScopeFactory>();
_scope = scopeFactory.CreateScope();
_context = _scope.ServiceProvider.GetService<ApplicationDbContext>();
var _user = User.getAppAdmin();
_context.Add(_user);
_context.SaveChanges(); //Here i got error on secound test. It says "An item with the same key has already been added"
}
public void Dispose()
{
_scope.Dispose();
_factory.Dispose();
_context.Dispose();
_client.Dispose();
}
使用 Guid 作为数据库名称时无法获取令牌。它说 username/password 无效。我使用 IdentityServer 进行身份验证
public async Task<string> GetAccessToken(string userName, string password, string clientId, string scope)
{
var disco = await _client.GetDiscoveryDocumentAsync("https://localhost:44444");
if (!String.IsNullOrEmpty(disco.Error))
{
throw new Exception(disco.Error);
}
var response = await _client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = clientId,
Scope = scope,
UserName = userName,
Password = password,
});
return response.AccessToken;
}
编辑: 您必须为每个测试指定一个唯一的名称运行 避免与 slightly mentioned here and and 共享同一个 InMemory 数据库。
尽管如此,下面切换到 "Constructor and Dispose" 的建议仍然适用。
IClassFixture
不是正确使用的工具,the documentation says:
When to use: when you want to create a single test context and share it among all the tests in the class, and have it cleaned up after all the tests in the class have finished.
在你的情况下,你必须使用“Constructor and Dispose”,文档说:
When to use: when you want a clean test context for every test (sharing the setup and cleanup code, without sharing the object instance).
因此您的测试将是:
public class AccountAdminTest
{
private readonly HttpClient _client;
private IServiceScopeFactory scopeFactory;
private readonly CustomWebApplicationFactory<Startup> _factory;
private ApplicationDbContext _context;
public AccountAdminTest(CustomWebApplicationFactory<Startup> factory)
{
//
_factory = new CustomWebApplicationFactory<Startup>();
_client = _factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = true,
BaseAddress = new Uri("https://localhost:44444")
});
scopeFactory = _factory.Services.GetService<IServiceScopeFactory>();
_scope = scopeFactory.CreateScope();
_context = scope.ServiceProvider.GetService<ApplicationDbContext>();
}
//Tests...
public void Dispose() {
_scope.Dispose();
_factory.Dispose();
//Dispose and cleanup anything else...
}
}
或者,您可以为 DbContext
using this overload of .AddDbContext
指定 ServiceLifetime.Transient
的生命周期
您只需更改此处的代码:
services.AddDbContext<ApplicationDbContext>((options, context) =>
{
context.UseInMemoryDatabase("IdentityDatabase");
});
而不是常量值 "IdentityDatabase"
,使用 Guid.NewGuid().ToString()
:
context.UseInMemoryDatabase(Guid.NewGuid().ToString());
然后,每次获取上下文时,都会使用新的内存数据库。
我正在 .NET Core 3.0 上使用 xunit 编写测试,内存数据库有问题。我需要为每个测试创建一个单独的数据库,但现在我创建了一个导致问题的数据库,但我不知道如何为每个测试创建一个新数据库。
public class AccountAdminTest : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly HttpClient _client;
private IServiceScopeFactory scopeFactory;
private readonly CustomWebApplicationFactory<Startup> _factory;
private ApplicationDbContext _context;
public AccountAdminTest(CustomWebApplicationFactory<Startup> factory)
{
_factory = factory;
_client = _factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = true,
BaseAddress = new Uri("https://localhost:44444")
});
scopeFactory = _factory.Services.GetService<IServiceScopeFactory>();
var scope = scopeFactory.CreateScope();
_context = scope.ServiceProvider.GetService<ApplicationDbContext>();
}
}
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
var descriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(DbContextOptions<ApplicationDbContext>));
if (descriptor != null)
{
services.Remove(descriptor);
}
services.AddDbContext<ApplicationDbContext>((options, context) =>
{
context.UseInMemoryDatabase("IdentityDatabase");
});
});
}
}
现在看起来像这样,但还是不行。当我在 AddDbContext 上更改生命周期时,它不会改变任何东西。
public class AccountAdminTest : IDisposable
{
public AccountAdminTest(ITestOutputHelper output)
{
this.output = output;
_factory = new CustomWebApplicationFactory<Startup>();
_client = _factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = true,
BaseAddress = new Uri("https://localhost:44444")
});
scopeFactory = _factory.Services.GetService<IServiceScopeFactory>();
_scope = scopeFactory.CreateScope();
_context = _scope.ServiceProvider.GetService<ApplicationDbContext>();
var _user = User.getAppAdmin();
_context.Add(_user);
_context.SaveChanges(); //Here i got error on secound test. It says "An item with the same key has already been added"
}
public void Dispose()
{
_scope.Dispose();
_factory.Dispose();
_context.Dispose();
_client.Dispose();
}
使用 Guid 作为数据库名称时无法获取令牌。它说 username/password 无效。我使用 IdentityServer 进行身份验证
public async Task<string> GetAccessToken(string userName, string password, string clientId, string scope)
{
var disco = await _client.GetDiscoveryDocumentAsync("https://localhost:44444");
if (!String.IsNullOrEmpty(disco.Error))
{
throw new Exception(disco.Error);
}
var response = await _client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = clientId,
Scope = scope,
UserName = userName,
Password = password,
});
return response.AccessToken;
}
编辑: 您必须为每个测试指定一个唯一的名称运行 避免与 slightly mentioned here and
尽管如此,下面切换到 "Constructor and Dispose" 的建议仍然适用。
IClassFixture
不是正确使用的工具,the documentation says:
When to use: when you want to create a single test context and share it among all the tests in the class, and have it cleaned up after all the tests in the class have finished.
在你的情况下,你必须使用“Constructor and Dispose”,文档说:
When to use: when you want a clean test context for every test (sharing the setup and cleanup code, without sharing the object instance).
因此您的测试将是:
public class AccountAdminTest
{
private readonly HttpClient _client;
private IServiceScopeFactory scopeFactory;
private readonly CustomWebApplicationFactory<Startup> _factory;
private ApplicationDbContext _context;
public AccountAdminTest(CustomWebApplicationFactory<Startup> factory)
{
//
_factory = new CustomWebApplicationFactory<Startup>();
_client = _factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = true,
BaseAddress = new Uri("https://localhost:44444")
});
scopeFactory = _factory.Services.GetService<IServiceScopeFactory>();
_scope = scopeFactory.CreateScope();
_context = scope.ServiceProvider.GetService<ApplicationDbContext>();
}
//Tests...
public void Dispose() {
_scope.Dispose();
_factory.Dispose();
//Dispose and cleanup anything else...
}
}
或者,您可以为 DbContext
using this overload of .AddDbContext
ServiceLifetime.Transient
的生命周期
您只需更改此处的代码:
services.AddDbContext<ApplicationDbContext>((options, context) =>
{
context.UseInMemoryDatabase("IdentityDatabase");
});
而不是常量值 "IdentityDatabase"
,使用 Guid.NewGuid().ToString()
:
context.UseInMemoryDatabase(Guid.NewGuid().ToString());
然后,每次获取上下文时,都会使用新的内存数据库。