如何为每个测试创建单独的内存数据库?

时间:2019-10-31 10:50:05

标签: c# asp.net-core testing xunit

我正在.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作为数据库名称时,我无法获得令牌。它说用户名/密码无效。我使用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;
        }

2 个答案:

答案 0 :(得分:1)

编辑:您必须为每个测试运行指定唯一的名称,以避免与slightly mentioned herealready answered herehere共享相同的InMemory数据库。

尽管如此,以下建议仍然适用于转换为“构造函数和处置”。


IClassFixture不是正确的工具,the documentation says

  

何时使用:当您要创建单个测试上下文并在类中的所有测试中共享它时,并在类中的所有测试完成后将其清除。

说明中,您必须使用“ Constructor and Dispose”,

  

何时使用:当您希望为每个测试提供干净的测试上下文时(共享设置和清理代码,不共享对象实例)。

所以您的测试将是:

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...
    }

}

或者,您可以为ServiceLifetime.Transient using this overload of .AddDbContext

指定生命周期DbContext

答案 1 :(得分:1)

您需要更改的只是此代码:

services.AddDbContext<ApplicationDbContext>((options, context) =>
{
    context.UseInMemoryDatabase("IdentityDatabase");
});

使用常量"IdentityDatabase"代替常量Guid.NewGuid().ToString()

context.UseInMemoryDatabase(Guid.NewGuid().ToString());

然后,每次获取上下文时,它将使用新的内存数据库。