2016-08-11 2 views
11

Я пытаюсь использовать базу данных InMemory EF7 для моего теста хранилища xunit.Как изолировать базу данных EF InMemory за тест XUnit

Но моя проблема в том, что когда я пытаюсь удалить существующий контекст, в памяти db сохраняются. Это означает, что один тест связан с другим.

Я прочитал эту статью Unit Testing Entity Framework 7 with the In Memory Data Store, и я попытался настроить контекст в конструкторе моего TestClass. Но такой подход не работает. Когда я запускаю тесты отдельно, все в порядке, но мой первый метод тестирования добавляет что-то в БД, а второй метод тестирования начинается с грязной БД из предыдущего метода тестирования. Я пытаюсь добавить 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 
{ 
    /// <summary> 
    /// Test for TaskRepository 
    /// </summary> 
    public class TaskRepositoryTests:IDisposable 
    { 

     private readonly DatabaseContext contextMemory; 

     /// <summary> 
     /// Constructor 
     /// </summary> 
     public TaskRepositoryTests() 
     { 
      var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>(); 
      optionsBuilder.UseInMemoryDatabase(); 
      contextMemory = new DatabaseContext(optionsBuilder.Options); 

     } 

     /// <summary> 
     /// Dispose DB 
     /// </summary> 
     public void Dispose() 
     { 
      //this has no effect 
      if (contextMemory != null) 
      {     
       contextMemory.Dispose(); 
      } 
     } 


     /// <summary> 
     /// Positive Test for ListByAssigneeId method 
     /// </summary> 
     /// <returns></returns>  
     [Fact] 
     public async Task TasksRepositoryListByAssigneeId() 
     { 
      // Arrange 
      var assigneeId = Guid.NewGuid(); 
      var taskList = new List<TaskItem>(); 


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

     } 

     /// <summary> 
     /// test for Add method 
     /// (Skip = "not able to clear DB context yet") 
     /// </summary> 
     /// <returns></returns> 
     [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" 
+1

Вы можете назвать вас dbs для каждого теста. См. Мой ответ здесь http://stackoverflow.com/questions/34925833/separate-in-memory-databases-ef7/38727634#38727634 –

+0

Привет, спасибо за ваш ответ, это может сработать ... Я попробую! –

+0

Совет. Чтобы создать и заполнить список задач TaskItems, проверьте ** GenFu **, например: 'var taskList = GenFu.GenFu.ListOf (20);' –

ответ

15

С documentation,

Обычно EF создает единый IServiceProvider для всех контекстов данность введите в AppDomain - все экземпляры контекста используют один и тот же экземпляр базы данных InMemory. Позволяя передавать данные, вы можете управлять областью базы данных InMemory.

Вместо того, чтобы тестовый класс одноразового использования и пытается избавиться контекста данных таким образом, создать новую для каждого теста:

private static DbContextOptions<BloggingContext> 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<DatabaseContext>(); 
    builder.UseInMemoryDatabase() 
      .UseInternalServiceProvider(serviceProvider); 

    return builder.Options; 
} 

Затем в каждом тесте, новый вверх контекста данных, используя этот метод:

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

Этот подход должен дать вам скрипучую чистую базу данных в памяти для каждого теста.

+0

Hallo Nate! спасибо! Это работает! Ты спасешь мой **! –

+0

@IvanMjartan С удовольствием! –

+0

Да, он выглядит великолепно ... но я разочарован тем, что это простое ограничение, например, не null и т. Д. Не работает :(Я думаю, что нет обходного пути или что-то есть? –

1

Я думаю, что ответ Нейта может быть устаревшим сейчас или, может быть, я делаю что-то неправильно. UseInMemoryDatabase() теперь требует имени db.

Ниже приведено то, с чем я столкнулся. Я добавил строку для создания уникального имени db. Я удалил операторы using в пользу использования конструктора и утилиты, которые вызываются один раз для каждого тестового примера.

От моего тестирования есть некоторые отладочные строки.

public class DeviceRepositoryTests : IClassFixture<DatabaseFixture>, 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<DeviceDbContext> 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<DeviceDbContext>(); 
     builder.UseInMemoryDatabase(dbName) 
       .UseInternalServiceProvider(serviceProvider); 

     return builder; 
    } 

Вот очень простой тестовый пример.

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

Я также сделал 8 тестовых примеров, которые пытались удалить одно и то же устройство с одним и тем же идентификатором и каждый переданный.

Смежные вопросы