XUnit — использование WebApplicationFactory с CollectionFixture для интеграционных тестов .Net Core

Я реализовал интеграционные тесты для своего API .Net Core с помощью xUnit, общих фиксаторов классов и внедрения зависимостей, чтобы я мог использовать WebApplicationFactory.

public class DatabaseFixture : WebApplicationFactory<Startup> {
    public DatabaseFixture(WebApplicationFactory<Startup>) {
        //Initialize Stuff
    }
}

public class SaveTests : IClassFixture<DatabaseFixture> {
    private DatabaseFixture databaseFixture;

    public SaveTests(DatabaseFixture databaseFixture)
    {
        this.applicationFixture = applicationFixture;
    }

    //Tests.....
}

public class SaveTests2 : IClassFixture<DatabaseFixture> {
    private DatabaseFixture databaseFixture;

    public SaveTests2(DatabaseFixture databaseFixture)
    {
        this.applicationFixture = applicationFixture;
    }

    //Tests.....
}

Это сработало хорошо, но тестовые данные инициализируются для каждого тестового файла.

Я хочу обновить свои тесты, чтобы использовать приспособление коллекции вместо приспособления класса, однако мне не удалось реализовать приспособление коллекции при внедрении WebApplicationFactory.

[CollectionDefinition("SaveTestCollection")]
public class SaveTestCollectionFixture : ICollectionFixture<DatabaseFixture>
{
}

public class DatabaseFixture : WebApplicationFactory<Startup> {
    public DatabaseFixture(WebApplicationFactory<Startup>) {
        //Initialize Stuff
    }
}

[Collection("SaveTestCollection")]
public class SaveTests
{
    DatabaseFixture fixture;

    public SaveTests(DatabaseFixture fixture)
    {
        this.fixture = fixture;
    }
}

Я получаю следующую ошибку:

System.AggregateException : One or more errors occurred. (Collection fixture type 'DatabaseFixture' had one or more unresolved constructor arguments: WebApplicationFactory) (The following constructor parameters did not have matching fixture data: DatabaseFixture fixture)
---- Collection fixture type 'DatabaseFixture' had one or more unresolved constructor arguments: WebApplicationFactory`1 factory
---- The following constructor parameters did not have matching fixture data: DatabaseFixture fixture

Я понимаю, что внедрение зависимостей не работает, но не знаю, как это исправить.

Итак, мой вопрос: могу ли я включить WebApplicationFactory через внедрение зависимостей или мне нужно изменить способ использования WebApplicationFactory для работы с фиксаторами коллекций?

Любая помощь очень ценится

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
456
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

Пример: у вас есть интеграционный тест, который извлекает сведения об учетной записи пользователя (уже заполненной) с именем «Джон Доу». Затем у вас есть еще один тест, который удаляет учетную запись пользователя с именем «Джон Доу». Предполагая, что у вас есть общая база данных, которая заполняется только один раз для всех тестов, эти тесты будут работать, только если они выполняются в определенном порядке, то есть сначала выборка, а затем удаление. Если вы измените порядок, выборка не удастся, поскольку при удалении теста был удален ресурс, который пытался получить тест выборки.

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

В моем случае использования я специально хочу иметь общую базу данных. Если я хочу что-то обновить или удалить, для этой цели в базе данных сохранен определенный объект. Я хочу иметь возможность организовывать свои тесты в отдельные группы и использовать приспособления для сбора данных для однократной генерации тестовых данных для каждой группы, и чтобы каждая группа использовала один и тот же набор данных.

JamesBV3 12.04.2024 23:39

Ну тогда похоже, что вы не представили всей картины. Если у вас есть конкретная организация, которая каким-то образом сводит риск, о котором я упомянул в своем ответе, к незначительному, то, возможно, вы можете попробовать подход с общей базой данных. Хотя я на собственном опыте убедился, что оно может укусить.

Prolog 13.04.2024 00:11

Рабочая демонстрация, которой вы можете следовать:

[CollectionDefinition("SaveTestCollection")]
public class SaveTestCollectionFixture : ICollectionFixture<DatabaseFixture>
{
}

public class DatabaseFixture : WebApplicationFactory<Startup>
{
    
}

[Collection("SaveTestCollection")]
public class SaveTests
{
    private readonly DatabaseFixture _fixture;

    public SaveTests(DatabaseFixture fixture)
    {
        _fixture = fixture;
    }
    [Fact]
    public async Task Test()
    {
        var client = _fixture.CreateClient();
        var response = await client.GetAsync(url);

    }

}

Поправьте меня, если я ошибаюсь, но здесь вообще не используется WebApplicationFactory? Похоже, что конструктор только что был удален.

JamesBV3 18.04.2024 23:10

Конечно, вы используете, просто не нужно использовать конструктор, DatabaseFixture сам по себе расширяет WebApplicationFactory, вы просто используете его, и этого достаточно.

Rena 19.04.2024 03:08
Ответ принят как подходящий

В итоге было реализовано следующее решение, которое задокументировано на странице XUnit Github:

  • Продолжить использование IClassFixture
  • Полностью запускайте конструктор фикстуры только в первый раз и сохраняйте статическую ссылку на полностью инициализированную фикстуру.
  • Любые дополнительные экземпляры конструктора прибора завершают работу раньше времени и не создают повторно тестовые данные.
  • Все тестовые файлы ссылаются на статическое приспособление, а не на приспособление, переданное в конструктор теста.
  • Фикстуру по-прежнему необходимо передать в конструктор теста, чтобы конструктор фикстуры полностью выполнялся, когда это необходимо.

Основная коллекция для предоставления WebApplicationFactory.

[CollectionDefinition("Tests")]
public class FixtureCollection : ICollectionFixture<WebApplicationFactory<Startup>>
{

}
  • Fixture использует Injected WebApplicationFactory для инициализации тестовых данных.
  • Теперь есть статическое свойство, которое хранит ссылку на экземпляр фикстуры.
  • Если статический экземпляр не был определен, запустите инициализацию приспособления и установите статический экземпляр для ссылки на этот экземпляр приспособления.
  • Если статический экземпляр определен, не запускайте инициализацию прибора.
  • Это гарантирует, что данные базы данных генерируются только один раз, независимо от того, сколько раз используется прибор.
public class SaveTestDatabaseFixture : WebApplicationFactory<Startup>{

    public static SaveTestDatabaseFixture Instance;

    public TestData first_test_data { get; set; }

    public SaveTestDatabaseFixture(WebApplicationFactory<Startup> factory)
    {
          if (Instance != null)
          {
              return;
          }

          var context = factory.GetContext();
          ......
          first_test_data = new TestData();
          context.TestData.Add(first_test_data);
          context.SaveChanges();

          Instance = this;
    }
}

Все тесты используют статический экземпляр базы данных Fixture.

[Collection("Tests")]
public class TestDataTests : IClassFixture<SaveTestDatabaseFixture> {
    private readonly SaveTestDatabaseFixture databaseFixture;

    public TestDataTests (SaveTestDatabaseFixture _)
    {
        this.databaseFixture = SaveTestDatabaseFixture.Instance;
    }

    [Fact]
    public async Task Test1()
    {
        var x = databaseFixture.TestData.Id;
        //Run Test
    }
}

[Collection("Tests")]
public class TestData2Tests : IClassFixture<SaveTestDatabaseFixture> {
    private readonly SaveTestDatabaseFixture databaseFixture;

    public TestData2Tests (SaveTestDatabaseFixture _)
    {
        this.databaseFixture = SaveTestDatabaseFixture.Instance;
    }

    [Fact]
    public async Task Test2()
    {
        var x = databaseFixture.TestData2.Id;
        //Run Test
    }
}

Поскольку у меня довольно много отдельных тестовых файлов, это изменение уменьшило объем данных, сохраняемых в тестовой базе данных, на 90% (с 2000 строк до 200 строк). Это также сократило время выполнения набора тестов (включая время установки базы данных) с 53 секунд до 20 секунд.

Другие вопросы по теме