如何在单元测试之间重置 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();
}
}
我正在尝试使用 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();
}
}