如何根据XUnit测试隔离EF InMemory数据库

我正在尝试使用InMemory EF7数据库进行我的xunit存储库测试。

但我的问题是,当我尝试Dispose创建的上下文时,内存db仍然存在。 这意味着一个测试涉及其他。

我已经阅读了本文unit testingentity framework7与内存数据存储 ,我试图在我的TestClass的构造函数中设置上下文。 但这种方法不起作用。 当我单独运行测试时一切正常,但我的第一个测试方法是在DB中添加一些东西,第二个测试方法从之前的测试方法开始使用脏DB。 我尝试将IDispose添加到测试类中,但方法DatabaseContext和DB在内存中保留。 我错了什么我错过了什么?

我的代码看起来像:

 using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Xunit; namespace Fabric.Tests.Repositories { ///  /// Test for TaskRepository ///  public class TaskRepositoryTests:IDisposable { private readonly DatabaseContext contextMemory; ///  /// Constructor ///  public TaskRepositoryTests() { var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseInMemoryDatabase(); contextMemory = new DatabaseContext(optionsBuilder.Options); } ///  /// Dispose DB ///  public void Dispose() { //this has no effect if (contextMemory != null) { contextMemory.Dispose(); } } ///  /// Positive Test for ListByAssigneeId method ///  ///  [Fact] public async Task TasksRepositoryListByAssigneeId() { // Arrange var assigneeId = Guid.NewGuid(); var taskList = new List(); //AssigneeId != assigneeId taskList.Add(new TaskItem() { AssigneeId = Guid.NewGuid(), CreatorId = Guid.NewGuid(), Description = "Descr 2", Done = false, Id = Guid.NewGuid(), Location = "Some location 2", Title = "Some title 2" }); taskList.Add(new TaskItem() { AssigneeId = assigneeId, CreatorId = Guid.NewGuid(), Description = "Descr", Done = false, Id = Guid.NewGuid(), Location = "Some location", Title = "Some title" }); taskList.Add(new TaskItem() { AssigneeId = assigneeId, CreatorId = Guid.NewGuid(), Description = "Descr 2", Done = false, Id = Guid.NewGuid(), Location = "Some location 2", Title = "Some title 2" }); //AssigneeId != assigneeId taskList.Add(new TaskItem() { AssigneeId = Guid.NewGuid(), CreatorId = Guid.NewGuid(), Description = "Descr 2", Done = false, Id = Guid.NewGuid(), Location = "Some location 2", Title = "Some title 2" }); //set up inmemory DB contextMemory.TaskItems.AddRange(taskList); //save context contextMemory.SaveChanges(); // Act var repository = new TaskRepository(contextMemory); var result = await repository.ListByAssigneeIdAsync(assigneeId); // Assert Assert.NotNull(result.Count()); foreach (var td in result) { Assert.Equal(assigneeId, td.AssigneeId); } } ///  /// test for Add method /// (Skip = "not able to clear DB context yet") ///  ///  [Fact] public async Task TasksRepositoryAdd() { var item = new TaskData() { AssigneeId = Guid.NewGuid(), CreatorId = Guid.NewGuid(), Description = "Descr", Done = false, Location = "Location", Title = "Title" }; // Act var repository = new TaskRepository(contextMemory); var result = await repository.Add(item); // Assert Assert.Equal(1, contextMemory.TaskItems.Count()); Assert.NotNull(result.Id); var dbRes = contextMemory.TaskItems.Where(s => s.Id == result.Id).SingleOrDefault(); Assert.NotNull(dbRes); Assert.Equal(result.Id, dbRes.Id); } } } 

我在用:

 "Microsoft.EntityFrameworkCore.InMemory": "1.0.0" "Microsoft.EntityFrameworkCore": "1.0.0" "xunit": "2.2.0-beta2-build3300" 

从文档中 ,

通常,EF为AppDomain中给定类型的所有上下文创建单个IServiceProvider – 这意味着所有上下文实例共享相同的InMemory数据库实例。 通过允许传入一个,您可以控制InMemory数据库的范围。

而不是使测试类是一次性的并尝试以这种方式处理数据上下文,为每个测试创建一个新的:

 private static DbContextOptions CreateNewContextOptions() { // Create a fresh service provider, and therefore a fresh // InMemory database instance. var serviceProvider = new ServiceCollection() .AddEntityFrameworkInMemoryDatabase() .BuildServiceProvider(); // Create a new options instance telling the context to use an // InMemory database and the new service provider. var builder = new DbContextOptionsBuilder(); builder.UseInMemoryDatabase() .UseInternalServiceProvider(serviceProvider); return builder.Options; } 

然后,在每个测试中,使用此方法新建数据上下文:

 using (var context = new DatabaseContext(CreateNewContextOptions())) { // Do all of your data access and assertions in here } 

这种方法应该为每个测试提供一个干净的内存数据库。

我认为Nate给出的答案现在可能已经过时,或者我做错了。 UseInMemoryDatabase()现在需要db名称。

以下是我最终的结果。 我添加了一行来创建一个唯一的数据库名称。 我删除了using语句,转而使用为每个测试用例调用一次的构造函数和dispose。

我的测试中有一些调试行。

 public class DeviceRepositoryTests : IClassFixture, IDisposable { private readonly DeviceDbContext _dbContext; private readonly DeviceRepository _repository; private readonly ITestOutputHelper _output; DatabaseFixture _dbFixture; public DeviceRepositoryTests(DatabaseFixture dbFixture, ITestOutputHelper output) { this._dbFixture = dbFixture; this._output = output; var dbOptBuilder = GetDbOptionsBuilder(); this._dbContext = new DeviceDbContext(dbOptBuilder.Options); this._repository = new DeviceRepository(_dbContext); DeviceDbContextSeed.EnsureSeedDataForContext(_dbContext); //_output.WriteLine($"Database: {_dbContext.Database.GetDbConnection().Database}\n" + _output.WriteLine($"" + $"Locations: {_dbContext.Locations.Count()} \n" + $"Devices: {_dbContext.Devices.Count()} \n" + $"Device Types: {_dbContext.DeviceTypes.Count()} \n\n"); //_output.WriteLine(deviceDbContextToString(_dbContext)); } public void Dispose() { _output.WriteLine($"" + $"Locations: {_dbContext.Locations.Count()} \n" + $"Devices: {_dbContext.Devices.Count()} \n" + $"Device Types: {_dbContext.DeviceTypes.Count()} \n\n"); _dbContext.Dispose(); } private static DbContextOptionsBuilder GetDbOptionsBuilder() { // The key to keeping the databases unique and not shared is // generating a unique db name for each. string dbName = Guid.NewGuid().ToString(); // Create a fresh service provider, and therefore a fresh // InMemory database instance. var serviceProvider = new ServiceCollection() .AddEntityFrameworkInMemoryDatabase() .BuildServiceProvider(); // Create a new options instance telling the context to use an // InMemory database and the new service provider. var builder = new DbContextOptionsBuilder(); builder.UseInMemoryDatabase(dbName) .UseInternalServiceProvider(serviceProvider); return builder; } 

这是一个非常基本的测试用例。

 [Fact] public void LocationExists_True() { Assert.True(_repository.LocationExists(_dbFixture.GoodLocationId)); } 

我还做了8个测试用例试图删除具有相同id的相同设备并且每个都通过了。