如何在单元测试之间重置EF7 InMemory提供程序?


78

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

Answers:


105

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

_context.Database.EnsureDeleted();

谢谢你,它解决了我的问题。我最初尝试了optionsBuilder.UseInMemoryDatabase(persist:false); 它已从EFCore中删除,然后偶然发现了另一个可能的解决方案,因为此处的测试之间具有不同的上下文:docs.efproject.net/en/latest/miscellaneous/testing.html 尽管测试根组成我更喜欢所选答案的简单性
马特·桑德斯(Matt Sanders)

20
这似乎并未重置内存数据库中的标识列。因此,如果您要在行中植入数据,则第一个测试将看到ID为1的行,第二个测试为2,以此类推。这是设计使然吗?
ssmith

3
现在是2019年,即使删除数据库并重新创建数据库之后ID仍然存在的问题仍然是一个问题!
汤姆(Tom),

真好 这解决了我的问题!我以为我的错误是我的测试正在并行运行,但实际上是内存数据库没有正确清除。
ThorkilVærge'19

我建议在下面也使用R4nc1d答案。您从内存中删除数据库,并确保每次使用新的标识列获取新数据库。只需使用[TearDown](在每次测试后运行)。
CularBytes

33

参加聚会有点晚,但我也遇到了同样的问题,但最终我要做的是。

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

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

这样,您不必添加

_context.Database.EnsureDeleted();

在所有测试中


7
难道这还不将它们保留在内存中吗?
桑德

1
是的,它将存在于内存中,但是如果将上下文包装在using语句中,它将自动被处理。
R4nc1d

1
除了最重要的答案之外,这还可以解决标识列重置的问题。我仍然会使用EnsureDeleted,只需[TearDown]在每次测试后将要运行的方法中添加一次,就不会太麻烦了。
CularBytes

7

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

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

新的InMemoryDatabaseRoot()创建了一个新数据库,而没有Id持续存在的问题。因此,您现在不需要:

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

为InMemoryDatabaseRoot +1。但是,仅在每个TestInitialize中使用TestCleanup并将上下文设置为null并重新创建一个新的上下文(假设您使用相同的数据库名称,而不使用InMemoryDatabaseRoot)将为您提供相同的内存数据库。
bobwah

2

我会结合两个答案。如果并行运行测试,则在运行另一个测试的过程中可能会删除一个数据库,因此在运行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();
  }
}

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

此处的示例通过RemoveRange实现此目的:https ://docs.microsoft.com/zh-cn/aspnet/core/test/integration-tests ? view = aspnetcore-3.1

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

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();
            }
      
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.