Интеграционные тесты веб-API ASP.NET Core 7 — WithHostBuilder разрывает соединение с базой данныхC#

Место общения программистов C#
Ответить Пред. темаСлед. тема
Anonymous
 Интеграционные тесты веб-API ASP.NET Core 7 — WithHostBuilder разрывает соединение с базой данных

Сообщение Anonymous »

Я работаю над веб-API ASP.NET Core 7 и пишу интеграционные тесты с использованием CustomWebApplicationFactory.
Моя программа выполняет несколько внешних HTTP-вызовов , поэтому я хочу посмеяться над ними в своих тестах. Я хочу иметь детальный контроль над тем, что я имитирую, и иметь возможность настраивать возвращаемые значения HTTP для каждого теста.
Вот почему я использую WithWebHostBuilder, а затем ConfigurationTestServices чтобы издеваться над HttpClient.
Моя проблема в том, что при простом добавлении вызова Factory.WithHostBuilder тесты начинают давать сбой, как если бы база данных ничего не сохраняется.
Я покажу вам упрощенный пример, который имитирует мой подход и в котором описанная мной проблема сохраняется.
Моя простая фабрика веб-приложений, заменяющая DbContext:
public class CustomWebApplicationFactory : WebApplicationFactory where TProgram : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
ServiceDescriptor? descriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(DbContextOptions));

if (descriptor is not null)
{
services.Remove(descriptor);
}

ServiceDescriptor? dbConnectionDescriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(DbConnection));

if (dbConnectionDescriptor != null)
{
services.Remove(dbConnectionDescriptor);
}

// Create open SqliteConnection so EF won't automatically close it.
services.AddSingleton(container =>
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();

return connection;
});

services.AddDbContext((container, options) =>
{
DbConnection connection = container.GetRequiredService();
options.UseSqlite(connection);
});
});

builder.UseEnvironment("test");
}
}

Мои тесты используют эту фабрику:
public class SetsTests : IClassFixture
{
private readonly CustomWebApplicationFactory _factory;

public SetsTests(CustomWebApplicationFactory factory)
{
_factory = factory;
}

[Fact]
public async Task Get_WithTwoSets_ReturnsJsonWithThem()
{
HttpClient client = _factory.CreateClient();

await using (AsyncServiceScope scope = _factory.Services.CreateAsyncScope())
{
ISetsRepository setsRepository = scope.ServiceProvider.GetRequiredService();
SetDto set1 = await setsRepository.CreateSetAsync("123");
SetDto set2 = await setsRepository.CreateSetAsync("456");
}

HttpResponseMessage response = await client.GetAsync("api/sets/");

Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var respType = response.Content.Headers.ContentType?.MediaType;
Assert.Equal("application/json", respType);
SetDto[]? content = await GetResponseContentAsync(response);
Assert.NotNull(content);
Assert.Equal(2, content.Length);
}
}

Этот простой тест пройден — я создаю 2 набора, а затем они правильно возвращаются из БД.
Но только с одним простым изменением (внедрить ложные сервисы), тесты Assert.Equal(2, content.Length); теперь завершаются неудачно; проверка — возвращаемый контент содержит 0 записей.
// code breaks if I replace
// HttpClient client = _factory.CreateClient();
// with
HttpClient client = _factory.WithWebHostBuilder(builder => { }).CreateClient();

Я понятия не имею, почему это вызывает это.
Мой Program.cs в моем API в этом примере очень прост:< /p>
using BrickFolio.Data;

using Microsoft.EntityFrameworkCore;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddEndpointsApiExplorer();

builder.Services.AddDataServices(builder.Configuration, builder.Environment);

WebApplication app = builder.Build();

if (builder.Environment.IsProduction() is false)
{
using IServiceScope scope = app.Services.CreateScope();
IServiceProvider services = scope.ServiceProvider;

try
{
SetsContext context = services.GetRequiredService();
context.Database.Migrate();
}
catch (Exception ex)
{
ILogger logger = services.GetRequiredService();
logger.LogError(ex, "An error occurred while migrating the database.");
}
}

app.MapControllers();

app.Run();

public partial class Program
{
}

Он только устанавливает контроллеры и вызывает AddsDataServices из проекта данных:
public static IServiceCollection AddDataServices(this IServiceCollection services, IConfiguration config, IHostEnvironment env)
{
DatabaseOptions? databaseOptions = config.GetRequiredSection(DatabaseOptions.SectionName)
.Get();

if (databaseOptions is null)
{
throw new ArgumentException("DatabaseOptions not found in configuration");
}

var dbPath = Path.Combine(env.ContentRootPath, databaseOptions.FileName);

services.AddDbContext(options =>
options.UseSqlite($"Data Source={dbPath}"));

services.AddScoped();

return services;
}

Это добавляет DbContext.
Я неправильно понимаю WithHostBuilder? Почему это привело к такому неожиданному поведению?
Я пробовал не использовать репозиторий, а просто использовать контексты в своих тестах, но результат был тот же.
Я также попробовал немного отладить его и использовать SQLite с файлами вместо параметра памяти. Я назначил уникальный Guid тестовой базе данных в каждом тесте:
services.AddSingleton(container =>
{
var dbFileName = $"test-{Guid.NewGuid()}.db";
var connection = new SqliteConnection($"Data Source={dbFileName}");
connection.Open();

return connection;
});

В результате я знаю, что без вызова WithHostBuilder он создает только один тестовый файл, содержащий записи.
Когда я вызовите WithHostBuilder, я получу два файла: один с записями, а второй пустой. Я подозреваю, что то же самое происходит, когда я использую источник данных в памяти - в итоге у меня есть два подключения к базе данных «в памяти», причем тесты используют одно соединение, а программа API - другое.
Я только не знаю, как с этим справиться.
РЕДАКТИРОВАТЬ: я понял, что не показал свой контроллер:
[ApiController]
[Route("api/sets")]
public class SetsController : ControllerBase
{
private readonly ISetsRepository _setsRepository;

public SetsController(ISetsRepository setsRepository)
{
_setsRepository = setsRepository;
}

// GET api/sets
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[Produces("application/json")]
public async Task Index()
{
IList sets = await _setsRepository.GetAllSetsAsync();
return sets;
}
}


Подробнее здесь: https://stackoverflow.com/questions/786 ... tabase-con
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «C#»