Как написать настоящий интеграционный тест в. NET Core? - PullRequest
1 голос
/ 09 апреля 2020

У меня два вопроса, но позвольте мне начать с самого начала. У меня ASP. NET Core 3.1 WebAPI, и я ищу наиболее оптимальный способ сквозного тестирования моих контроллеров ( Я не хочу использовать InMemory provider ) , Я разделил свои тесты на два набора: «Только чтение» (GET) и «Чтение-запись» (POST, PUT, PATCH, DELETE). Для тестов только для чтения я хочу создать совершенно новую базу данных (я использую миграцию вначале кода) один раз, а затем проверяю все GET запросы один за другим. Для запросов на чтение и запись я хочу создать новую базу данных и удалить ее после теста.

Это то, что я сделал до сих пор:

public class TestFixture<TStartup> : IDisposable where TStartup : class
{
    private readonly TestServer _server;

    public TestFixture()
    {
        var builder = new WebHostBuilder().UseStartup<TStartup>();
        builder.ConfigureAppConfiguration((context, conf) =>
        {
            conf.AddUserSecrets(typeof(Startup).GetTypeInfo().Assembly);
        });

        _server = new TestServer(builder);

        Client = _server.CreateClient();
        Client.BaseAddress = new Uri("http://localhost:5000");
    }

    public HttpClient Client { get; }

    public void Dispose()
    {
        Client.Dispose();
        _server.Dispose();
    }
}

... и тест :

public class TestControllerShould : IClassFixture<TestFixture<Startup>>
{
    public HttpClient Client { get; }

    public TestControllerShould(TestFixture<Startup> fixture)
    {
        Client = fixture.Client;
    }

    [Fact]
    public async Task GetHelloWorld()
    {
        // Arrange
        var request = new HttpRequestMessage(new HttpMethod("GET"), "/test/");

        // Act
        var response = await Client.SendAsync(request);

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var content = await response.Content.ReadAsStringAsync();
        Assert.Equal("Hello World!", content);
    }
}

У меня две проблемы. Прежде всего, я использую Startup из основного проекта (что желательно, потому что я хочу его протестировать) со строкой производственного соединения, так что это одна проблема. Вторая проблема, я хотел бы создавать и удалять базу данных после каждого теста «записи», чего я не могу по понятным причинам. Итак, мои вопросы:

  1. Как я могу использовать Startup класс, но изменить только имя базы данных на "MyDatabaseName + Guid.NewGuid()"?
  2. Второй вопрос, как я могу создать и удалить базу данных (с уникальным именем) до и после каждого теста?

PS. Я не хочу использовать InMemory провайдера. Я также не хочу использовать транзакцию и откат в конце теста. Я хочу сделать реальный интеграционный тест.

1 Ответ

0 голосов
/ 09 апреля 2020

Если я правильно понимаю последнюю документацию для ASP. NET Core 3.1, теперь мы должны использовать WebApplicationFactory<TStartup>.

Вы можете переопределить его ConfigureWebHost метод для изменения конфигурации, зависимостей и так далее. Я недавно сделал это, заменив реальную реализацию базы данных на Fake:

public class RestaurantApiFactory : WebApplicationFactory<Startup>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        if (builder is null)
            throw new ArgumentNullException(nameof(builder));

        builder.ConfigureServices(services =>
        {
            var descriptors = services
                .Where(d =>
                    d.ServiceType == typeof(IReservationsRepository))
                .ToList();
            foreach (var d in descriptors)
                services.Remove(d);

            services.AddSingleton<IReservationsRepository>(
                new FakeDatabase());
        });
    }
}

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

Вы используете фабрику так:

using var factory = new RestaurantApiFactory();
var client = factory.CreateClient();

Похоже, вы используете xUnit. net. Если это так, вы можете использовать его BeforeAfterTestAttribute для создания и удаления баз данных для каждого теста.

Вот способ, которым я обычно делаю это:

public class UseDatabaseAttribute : BeforeAfterTestAttribute
{
    public override void Before(MethodInfo methodUnderTest)
    {
        using (var schemaStream = ReadSchema())
        using (var rdr = new StreamReader(schemaStream))
        {
            var schemaSql = rdr.ReadToEnd();

            var builder = new SqlConnectionStringBuilder(
                ConnectionStrings.Reservations);
            builder.InitialCatalog = "Master";
            using (var conn = new SqlConnection(builder.ConnectionString))
            using (var cmd = new SqlCommand())
            {
                conn.Open();
                cmd.Connection = conn;

                foreach (var sql in SeperateStatements(schemaSql))
                {
                    cmd.CommandText = sql;
                    cmd.ExecuteNonQuery();
                }
            }
        }

        base.Before(methodUnderTest);
    }

    private Stream ReadSchema()
    {
        return typeof(SqlReservationsProgramVisitor<>)
            .Assembly
            .GetManifestResourceStream(
                "Ploeh.Samples.BookingApi.Sql.BookingDbSchema.sql");
    }

    private static IEnumerable<string> SeperateStatements(string schemaSql)
    {
        return schemaSql.Split(
            new[] { "GO" },
            StringSplitOptions.RemoveEmptyEntries);
    }

    public override void After(MethodInfo methodUnderTest)
    {
        base.After(methodUnderTest);

        var dropCmd = @"
            IF EXISTS (SELECT name
                FROM master.dbo.sysdatabases
                WHERE name = N'Booking')
            DROP DATABASE[Booking];";

        var builder = new SqlConnectionStringBuilder(
            ConnectionStrings.Reservations);
        builder.InitialCatalog = "Master";
        using (var conn = new SqlConnection(builder.ConnectionString))
        using (var cmd = new SqlCommand(dropCmd, conn))
        {
            conn.Open();
            cmd.ExecuteNonQuery();
        }
    }
}

Вы можете увидеть его в использовании здесь .

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