Як я можу скинути постачальника EF7 InMemory між модульними тестами?


81

Я намагаюся використовувати постачальника EF7 InMemory для модульних тестів, але постійний характер бази даних InMemory між тестами викликає у мене проблеми.

Наступний код демонструє мою проблему. Один тест буде працювати, а другий тест завжди буде невдалим. Незважаючи на те, що я встановив для _context значення null між тестами, у другому тестовому запуску завжди буде 4 записи.

[TestClass]
public class UnitTest1
{

    private SchoolContext _context;

    [TestInitialize]
    public void Setup()
    {
        Random rng = new Random();

        var optionsBuilder = new DbContextOptionsBuilder<SchoolContext>();
        optionsBuilder.UseInMemoryDatabase();

        _context = new SchoolContext(optionsBuilder.Options);
        _context.Students.AddRange(
            new Student { Id = rng.Next(1,10000), Name = "Able" },
            new Student { Id = rng.Next(1,10000), Name = "Bob" }
        );
        _context.SaveChanges();
    }

    [TestCleanup]
    public void Cleanup()
    {
        _context = null;
    }

    [TestMethod]
    public void TestMethod1()
    {
        Assert.AreEqual(2, _context.Students.ToList().Count());
    }

    [TestMethod]
    public void TestMethod2()
    {
        Assert.AreEqual(2, _context.Students.ToList().Count());
    }

}

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class SchoolContext : DbContext
{
    public SchoolContext(DbContextOptions options) : base(options) { }

    public DbSet<Student> Students { get; set; }
}

Відповіді:


110

Наступний виклик очистить сховище даних у пам’яті.

_context.Database.EnsureDeleted();

Дякую за це, це вирішило мої проблеми. Я ініціалізував спробу optionsBuilder.UseInMemoryDatabase (persist: false); який був вилучений з EFCore, а потім наткнувся на інше можливе рішення для різних контекстів між тестами тут: docs.efproject.net/en/latest/miscellaneous/testing.html Я віддаю перевагу простоті вибраної відповіді, хоча для складу кореневого тесту
Matt Sanders

22
Здається, це не скидає ідентифікаційний стовпець бази даних у пам'яті. Отже, якщо ви засіваєте дані рядком, перший тест побачить рядок з ідентифікатором 1, другий тест 2 і т. Д. Це за задумом?
ssmith

4
Настав 2019 рік, і проблема з тим, що Ідентифікатор зберігається навіть після того, як база даних відкидається і відтворюється, все ще залишається проблемою!
Том,

Приємно. Це вирішило мою проблему! Я вважав, що моя помилка полягала в тому, що мої тести працювали паралельно, але насправді це те, що база даних в пам'яті не очищалася належним чином.
Thorkil Værge

Я б запропонував використовувати також відповідь R4nc1d нижче. Ви видаляєте базу даних з пам'яті, а також переконуєтесь, що кожен раз отримуєте нову базу даних зі свіжим стовпчиком ідентифікації. Просто використовуйте [TearDown](що запускається після кожного тесту).
CularBytes

34

Трохи пізно на вечірку, але я теж зіткнувся з тим самим питанням, але те, що я в підсумку зробив, було.

Вказівка ​​різного імені бази даних для кожного тесту.

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

Таким чином вам не потрібно додавати

_context.Database.EnsureDeleted();

у всіх ваших тестах


7
Чи це все ще не залишає їх у пам’яті?
Сандер,

1
Так буде жити в пам'яті, але якщо ви обернете контекст у оператор, він автоматично утилізується.
R4nc1d

1
Чудове доповнення до верхньої відповіді, це може бути використано для вирішення ідентифікації скидання стовпця. Я б все-таки використовував EnsureDeleted, потрібно лише додати це один раз у [TearDown]методі, який буде виконуватися після кожного тесту, так що не дуже боляче.
CularBytes

8

Просто змініть визначення коду DbContextOptionsBuilder таким чином:

        var databaseName = "DatabaseNameHere";
        var dbContextOption = new DbContextOptionsBuilder<SchoolContext>()
                                    .UseInMemoryDatabase(databaseName, new InMemoryDatabaseRoot())
                                    .Options;

new InMemoryDatabaseRoot () створює нову базу даних без проблеми збереження ідентифікатора. Тож вам зараз не потрібно для:

       [TestCleanup]
       public void Cleanup()
       {
           _context = null;
       }

+1 для InMemoryDatabaseRoot. Однак просто використання TestCleanup і встановлення контексту як нульовий і повторне створення нового контексту (припускаючи, що ви використовуєте одне і те ж ім'я бази даних, а не використовуєте InMemoryDatabaseRoot) у кожному TestInitialize дасть вам однакову базу даних в пам'яті.
бобва

3

Я б запропонував поєднання обох відповідей. Якщо тести виконуються паралельно, ви можете видалити базу даних, коли ви перебуваєте в центрі запуску іншого тесту, тому я бачив спорадичні збої під час запуску тестів 30+.

Дайте йому випадкове ім’я db та переконайтеся, що воно видаляється після завершення тесту.

public class MyRepositoryTests : IDisposable {
  private SchoolContext _context;

  [TestInitialize]
  public void Setup() {
    var options = new DbContextOptionsBuilder<ApplicationDbContext>()
      // Generate a random db name
      .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
      .Options;
      _context = new ApplicationDbContext(options);
  }

  [TestCleanup]
  public void Cleanup()
    _context.Database.EnsureDeleted(); // Remove from memory
    _context.Dispose();
  }
}

2

Я використовую DbContextкріплення, як показано нижче

public class DbContextFixture 
    where TDbContext : DbContext
{
    private readonly DbContextOptions _dbContextOptions = 
        new DbContextOptionsBuilder()
            .UseInMemoryDatabase("_", new InMemoryDatabaseRoot())
            .Options;

    public TDbContext CreateDbContext()
    {
        return (TDbContext)(typeof(TDbContext)
            .GetConstructor(new[] { typeof(DbContextOptions) })
            .Invoke(new[] { _dbContextOptions }));
    }
}

тепер ви можете просто зробити

public class MyRepositoryTests : IDisposable {
    private SchoolContext _context;
    private DbContextFixture<ApplicationDbContext> _dbContextFixture;

    [TestInitialize]
    public void Setup() {
        _dbContextFixture = new DbContextFixture<ApplicationDbContext>();
        _context = _dbContextFixture.CreateDbContext();
        _context.Students.AddRange(
            new Student { Id = rng.Next(1,10000), Name = "Able" },
            new Student { Id = rng.Next(1,10000), Name = "Bob" }
        );
        _context.SaveChanges();
    }

    [TestCleanup]
    public void Cleanup()
        _context.Dispose();
        _dbContextFixture = null;
    }

    [TestMethod]
    public void TestMethod1()
    {
        Assert.AreEqual(2, _context.Students.ToList().Count());
    }

    [TestMethod]
    public void TestMethod2()
    {
        Assert.AreEqual(2, _context.Students.ToList().Count());
    }
}

Це рішення безпечне для різьблення. Докладніше див. У моєму блозі .



0

Ось мій підхід у 2 центи, щоб тримати кожен модульний тест ізольовано один від одного. Я використовую C # 7, XUnit та EF core 3.1.

Зразок класу TestFixture.

public class SampleIntegrationTestFixture : IDisposable
    {

        public DbContextOptionsBuilder<SampleDbContext> SetupInMemoryDatabase()
            => new DbContextOptionsBuilder<SampleDbContext>().UseInMemoryDatabase("MyInMemoryDatabase");

 private IEnumerable<Student> CreateStudentStub()
            => new List<Student>
            {
            new Student { Id = rng.Next(1,10000), Name = "Able" },
            new Student { Id = rng.Next(1,10000), Name = "Bob" }
            };

        public void Dispose()
        {
        }
   }

Зразок класу IntegrationTest

 public class SampleJobIntegrationTest : IClassFixture<SampleIntegrationTestFixture >
 {
    private DbContextOptionsBuilder<SampleDbContext> DbContextBuilder { get; }
    private SampleDbContext SampleDbContext { get; set; }

 public SampleJobIntegrationTest(SampleIntegrationTestFixture 
 sampleIntegrationTestFixture )
  {
        SampleIntegrationTestFixture = sampleIntegrationTestFixture ;

        SampleDbContextBuilder = sampleIntegrationTestFixture .SetupInMemoryDatabase();
  }



  [Fact]
    public void TestMethod1()
    {
using(SampleDbContext = new SampleDbContext(SampleDbContextBuilder.Options))

        var students= SampleIntegrationTestFixture.CreateStudentStub();
            {
            SampleDbContext.Students.AddRange(students);

        SampleDbContext.SaveChanges();

  Assert.AreEqual(2, _context.Students.ToList().Count());

                SampleDbContext.Database.EnsureDeleted();
            }
      
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.