Как создать отдельную в памяти базу данных для каждого теста? - PullRequest
0 голосов
/ 31 октября 2019

Я пишу тест с xunit на .NET Core 3.0, и у меня проблема с базой данных в памяти. Мне нужна отдельная база данных для каждого теста, но сейчас я создаю одну базу данных, которая вызывает проблемы, но я не знаю, как создать новую базу данных для каждого теста.

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 ]

1 голос
/ 31 октября 2019

Все, что вам нужно изменить, это код здесь:

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

Вместо постоянного значения "IdentityDatabase" используйте что-то вроде Guid.NewGuid().ToString():

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

Затем каждыйкогда контекст получен, он будет использовать новую базу данных в памяти.

1 голос
/ 31 октября 2019

Редактировать: Вы должны указать уникальное имя для каждого запуска теста, чтобы избежать совместного использования той же базы данных InMemory, что и , немного упомянутой здесь и , на которую здесь уже даны ответы и здесь .

Тем не менее, приведенное ниже предложение переключиться на «Конструктор и утилизация» по-прежнему применимо.


IClassFixture не подходит для использования, документация гласит :

Когда использовать: когда вы хотите создать отдельный контекст теста и поделиться им среди всех тестов в классе и очистить его после всех тестов вкласс закончен.

В вашем случае вы должны использовать « Конструктор и Утилизация », документация гласит:

Когда использовать: когда вы хотите чиститьконтекст теста для каждого теста (общий доступ к коду настройки и очистки, без совместного использования экземпляра объекта ).

Таким образом, ваш тест будет:

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 для вашего DbContext , используя эту перегрузку .AddDbContext

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...