2016-08-11 10 views
11

Próbuję użyć bazy danych InMemory EF7 do testu mojego repozytorium.Jak wyizolować bazę danych EF InMemory na jeden test XUnit

Ale mój problem polega na tym, że gdy próbuję usunąć utworzony kontekst, db w pamięci pozostaje. Oznacza to, że jedno badanie dotyczy innego.

Czytałem ten artykuł Unit Testing Entity Framework 7 with the In Memory Data Store i próbowałem konfiguracji kontekstu w konstruktorze moim TestClass. Ale to podejście nie działa. Kiedy przeprowadzam testy oddzielnie wszystko jest w porządku, ale moja pierwsza metoda testowa dodaje coś do DB, a druga metoda testowa zaczyna się od brudnej DB z poprzedniej metody testowej. Próbuję dodać IDispose do klasy testowej, ale metoda DatabaseContext i DB trwa w pamięci. Co robię źle, brakuje mi czegoś?

Mój kod wygląda następująco:

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


    } 
} 

Używam:

"Microsoft.EntityFrameworkCore.InMemory": "1.0.0" 

"Microsoft.EntityFrameworkCore": "1.0.0" 

"xunit": "2.2.0-beta2-build3300" 
+1

Możesz nazywać Cię DSB za każdy test. Zobacz moją odpowiedź tutaj http://stackoverflow.com/questions/34925833/separate-in-memory-databases-ef7/38727634#38727634 –

+0

Cześć dzięki za odpowiedź, może to zadziałać ... Spróbuję! –

+0

Wskazówka: aby generować i wypełnić tasklist z TaskItems, sprawdź ** GenFu **, np: 'var tasklist = GenFu.GenFu.ListOf (20);' –

Odpowiedz

15

Z documentation,

Zazwyczaj EF tworzy pojedynczy IServiceProvider dla wszystkich kontekstów danego wpisz AppDomain - co oznacza, że ​​wszystkie instancje kontekstu współużytkują tę samą instancję bazy danych InMemory. Po zezwoleniu na przekazanie można kontrolować zakres bazy danych InMemory.

Zamiast klasa Test jednorazowe i próbuje zbyć kontekstu danych w ten sposób utworzyć nowy dla każdego testu:

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

Następnie, w każdym teście, nowy się kontekst danych przy użyciu ta metoda:

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

Podejście to powinno zapewnić Ci bazę danych o piskliwym, czystym zapisie w pamięci dla każdego testu.

+0

Hallo Nate! Dziękuję bardzo! To działa! Uratujesz moje **! –

+0

@IvanMjartan Z przyjemnością pomożemy! –

+0

Tak, wygląda świetnie ... ale jestem rozczarowany, ponieważ to proste ograniczenie, takie jak not null itp., Nie działa :(Myślę, że nie ma obejścia, czy coś tam jest? –

1

Myślę, że odpowiedź, którą dał Nate, może być już nieaktualna lub może robię coś nie tak. UseInMemoryDatabase() wymaga teraz nazwy bazy danych.

Poniżej jest to, co otrzymałem. Dodałem linię, aby utworzyć unikalną nazwę bazy danych. Usunąłem instrukcje użycia na rzecz użycia konstruktora i utylizacji, które są wywoływane raz dla każdego przypadku testowego.

Istnieje kilka linii debugowania z mojego testowania.

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

Oto bardzo prosty przypadek testowy.

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

Wykonałem również 8 przypadków testowych, które próbowały usunąć to samo urządzenie z tym samym identyfikatorem i każde minięcie.

Powiązane problemy