【问题标题】:How can I reset an EF7 InMemory provider between unit tests?如何在单元测试之间重置 EF7 InMemory 提供程序?
【发布时间】:2016-02-03 02:35:57
【问题描述】:

我正在尝试使用 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; }
}

【问题讨论】:

    标签: c# unit-testing entity-framework-core mstest


    【解决方案1】:

    以下调用将清除内存中的数据存储。

    _context.Database.EnsureDeleted();
    

    【讨论】:

    • 谢谢你,它解决了我的问题。我最初尝试了 optionsBuilder.UseInMemoryDatabase(persist: false);它已从 EFCore 中删除,然后偶然发现了另一种可能的解决方案,用于在此处的测试之间具有不同的上下文:docs.efproject.net/en/latest/miscellaneous/testing.html 尽管对于测试根组合,我更喜欢所选答案的简单性
    • 这似乎不会重置内存数据库的识别列。因此,如果您使用一行播种数据,第一个测试将看到 id 为 1 的行,第二个测试将看到 2 等。这是设计使然吗?
    • 现在是 2019 年,即使删除并重新创建数据库后,ID 仍然存在的问题仍然是一个问题!
    • 不错。这解决了我的问题!我以为我的错误是我的测试是并行运行的,但实际上是内存数据库没有被正确清除。
    • 我建议也使用下面的 R4nc1d 答案。您从内存中删除数据库,并确保每次都使用新的标识列获得一个新数据库。只需使用[TearDown](在每次测试后运行)。
    【解决方案2】:

    聚会迟到了,但我也遇到了同样的问题,但我最终做的是。

    为每个测试指定不同的数据库名称。

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

    这样就不用加了

    _context.Database.EnsureDeleted();
    

    在你所有的测试中

    【讨论】:

    • 这不是还在记忆中吗?
    • 是的,它会存在于内存中,但是如果您将上下文包装在 using 语句中,它将自动被释放。
    • 对最佳答案的很好补充,这可用于解决识别列重置。我仍然会使用EnsureDeleted,只需在[TearDown] 方法中添加一次,该方法将在每次测试后运行,所以不会很痛苦。
    • 我是使用 EF Core 进行单元测试的新手(现在使用 EF Core 5)。为了简单起见,我选择了这种方法。我也有少量的测试。通过为每次测试运行指定一个唯一的名称,我担心在进行数百次测试时这样做会产生什么影响。有人知道吗?
    【解决方案3】:

    我会选择两个答案的组合。如果测试并行运行,您可能会在运行另一个测试的过程中删除一个数据库,所以我在运行 30 多个测试时看到了零星的失败。

    给它一个随机的数据库名称,并确保它在测试完成时被删除。

    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();
      }
    }
    

    【讨论】:

      【解决方案4】:

      我使用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());
          }
      }
      

      此解决方案是线程安全的。详情请见我的blog

      【讨论】:

        【解决方案5】:

        只需将 DbContextOptionsBuilder 的代码定义更改为如下所示:

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

        new InMemoryDatabaseRoot() 创建一个新数据库,而不会出现 Id 持久化的问题。 所以你现在不需要:

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

        【讨论】:

        • +1 表示 InMemoryDatabaseRoot。但是,仅使用 TestCleanup 并将上下文设置为 null 并在每个 TestInitialize 中重新创建一个新上下文(假设您使用相同的数据库名称,并且不使用 InMemoryDatabaseRoot)将为您提供相同的内存数据库。
        • 很好,这个解决方案重置了身份列。
        【解决方案6】:

        这里的例子是通过 RemoveRange 实现的:https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1

        db.<Entity>.RemoveRange(db.<entity>);
        

        【讨论】:

          【解决方案7】:

          这是我的 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()
                  {
                  }
             }
          

          示例集成测试类

           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();
                      }
                
              }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2015-03-16
            • 1970-01-01
            • 1970-01-01
            • 2019-08-11
            • 1970-01-01
            • 1970-01-01
            • 2017-04-26
            相关资源
            最近更新 更多