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

How can I reset an EF7 InMemory provider between unit tests?

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

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

_context.Database.EnsureDeleted();

晚会有点晚了,但我也 运行 遇到了同样的问题,但我最终做的是。

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

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

这样你就不必添加

_context.Database.EnsureDeleted();

在你所有的测试中

我会结合这两个答案。如果并行测试 运行,您可能会在 运行 进行另一项测试时删除数据库,因此当 运行 进行 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();
  }
}

我使用 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

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

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

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

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

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

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

这是我的 2 美分方法,可以使每个单元测试彼此隔离。我正在使用 C# 7、XUnit 和 EF 核心 3.1。

示例测试夹具 class.

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

样本集成测试class

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