Избегайте начального DbContext HasData в EF Core 2.2 во время модульных тестов

#sqlite #unit-testing #asp.net-core #entity-framework-core #xunit

#sqlite #модульное тестирование #asp.net-core #entity-framework-core #xunit

Вопрос:

В моем ASP.Net CORE 2.2 / EF Core 2.2 web API app, у меня есть метод HasData () в моем DbContext для заполнения базы данных некоторыми стандартными данными, которые я использую. Однако я не хочу использовать эти данные при запуске моих тестов xUnit.

В моих модульных тестах используется поставщик Sqlite в памяти, и как часть этого процесса требуется вызов EnsureCreated(). Что ж, EnsureCreated () вызывает OnModelCreating(), который вызывает HasData (), так что мой контекст модульного теста теперь содержит все мои исходные данные HasData, которые мне не нужны. Я хочу заполнить свои модульные тесты разными, очень специфичными данными.

Поскольку EnsureCreated () заполняет контекст, а затем я пытаюсь добавить исходные данные, относящиеся к моему модульному тестированию, в итоге я получаю оба набора данных в моем тестовом DbContext и мои тесты завершаются неудачей.

Как я могу обойти вызов HasData для моих модульных тестов?

Комментарии:

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

2. @PaoloGo, я начал с обычного поставщика InMemory, но столкнулся с проблемой, потому что он не сбрасывает приращение идентификатора между параллельно выполняемыми тестами, поэтому я получал непредсказуемые идентификаторы первичного ключа.

3. У меня также возникла проблема с этим, lol. Вы можете обойти это, протестировав PK вставленного объекта вместо постоянного значения. например, var foo = ctx.Add(new Foo()); ... Assert.Equal(foo.Id, actualId); Не должно быть проблем в EF Core v3

4. Пожалуйста, дайте мне знать, если вы получите решение исходной проблемы. В противном случае мы могли бы открыть проблему в репозитории 🙂

5. Другим обходным путем для исходной проблемы является явное удаление начального значения, например ctx.RemoveRange(ctx.Foo);

Ответ №1:

Вместо того, чтобы пытаться обойти HasData(), вы могли бы условно не предоставлять данные этому методу.

Краткий пример — если вы переместите предварительно загруженные данные, например, в классы «DataInitializer»:

  builder.HasData(new UserDataInitialiser().Data); 
  

Затем установите статический флаг в базовом классе:

 public abstract class DataInitialiserControl
{
    public static bool SkipInitData { get; set; } // ** flag **
}

public abstract class DataInitialiser<T> : DataInitialiserControl
{
    public IList<T> Data => SkipInitData ? new List<T>() : GetData();

    protected abstract IList<T> GetData();
}
  

Ваши инициализаторы данных будут выглядеть следующим образом:

 public class UserDataInitialiser : DataInitialiser<User>
{
    protected override IList<User> GetData()
    {
        return new[]
        {
            new User {Id = 1, Name = "Bob"}
        };
    }
}
  

Затем вы могли бы просто установить статический флаг при инициализации теста:

 public abstract class TestBase
{
    protected DbContextOptions<MyContext> DbOptions { get; private set; }

    [TestInitialize]
    public void InitializeDatabase()
    {
        // ** SKIP DATA PRE-POP **
        DataInitialiserControl.SkipInitData = true;

        DbOptions = BuildDbContextOptions(new DbContextOptionsBuilder<MyContext>()).Options;

        using (var context = GetContext())
        {
            context.Database.EnsureCreated();
        }
    }

    [TestCleanup]
    public void ClearDatabase()
    {
        using (var context = GetContext())
        {
            context.Database.EnsureDeleted();
        }
    }
}
  

(Код непроверенный, но должен быть более или менее правильным).

Ответ №2:

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

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

Вот несколько полезных примеров издевательства: написание модульных тестов с помощью NUnit и Moq и введение в модульное тестирование с помощью mocks (с использованием moq).

Я также подозреваю, что атрибут теории и встроенные данные могут быть вам полезны. Создание параметризованных тестов в xUnit

Надеюсь, это поможет.