2016-08-11 1 views
11

J'essaie d'utiliser la base de données InMemory EF7 pour mon test de référentiel xunit. Mais mon problème est que quand j'essaye de Disposer le contexte créé, la mémoire dans la mémoire persiste. Cela signifie qu'un test implique d'autres.Comment isoler la base de données EF InMemory par test XUnit

J'ai lu cet article Unit Testing Entity Framework 7 with the In Memory Data Store et j'ai essayé de configurer le contexte dans le constructeur de ma classe de test. Mais cette approche ne fonctionne pas. Lorsque j'exécute les tests séparément, tout est OK, mais ma première méthode de test ajoute quelque chose dans la base de données et la deuxième méthode de test commence avec la base de données sale de la méthode de test précédente. J'essaye d'ajouter IDispose dans la classe de test mais la méthode DatabaseContext et DB persistent dans la mémoire. Qu'est-ce que je fais mal est-ce que je manque quelque chose?

Mon code ressemble:

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


    } 
} 

J'utilise:

"Microsoft.EntityFrameworkCore.InMemory": "1.0.0" 

"Microsoft.EntityFrameworkCore": "1.0.0" 

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

Vous pouvez vous nommer dbs pour chaque test. Voir ma réponse ici http://stackoverflow.com/questions/34925833/separate-in-memory-databases-ef7/38727634#38727634 –

+0

Salut merci pour votre réponse cela pourrait fonctionner ... Je vais l'essayer! –

+0

Conseil: pour générer et remplir la TaskList de TaskItems, consultez ** GenFu **, par exemple: 'var taskList = GenFu.GenFu.ListOf (20);' –

Répondre

15

De l'documentation,

En règle générale, EF crée un seul IServiceProvider pour tous les contextes d'une donnée tapez dans un AppDomain - signifiant que toutes les instances de contexte partagent la même instance de base de données InMemory. En autorisant la transmission, vous pouvez contrôler la portée de la base de données InMemory.

Au lieu de faire la classe de test jetable et d'essayer de disposer du contexte de données de cette façon, créer un nouveau pour chaque test:

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

Ensuite, dans chaque essai, nouveau un contexte de données à l'aide cette méthode:

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

Cette approche devrait vous procurer une base de données en mémoire propre pour chaque test.

+0

Hallo Nate! Merci beaucoup! Ça fonctionne! Vous économisez mon a **! –

+0

@IvanMjartan Heureux de vous aider! –

+0

Oui ça a l'air génial ... mais je suis déçu parce que cette contrainte simple comme non null etc ne fonctionne pas :(Je pense qu'il n'y a pas de solution de contournement ou y at-il quelque chose? –

1

Je pense que la réponse donnée par Nate est peut-être dépassée maintenant ou peut-être que je fais quelque chose de mal. UseInMemoryDatabase() nécessite maintenant un nom de base de données.

Voici ce que j'ai fini avec. J'ai ajouté une ligne pour créer un nom de base de données unique. J'ai supprimé les instructions using en faveur de l'utilisation du constructeur et de la disposition qui sont appelées une fois pour chaque cas de test.

Il y a quelques lignes de débogage dans mes tests.

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

Voici un cas de test très basique. J'ai également fait 8 des cas de test qui ont tenté de supprimer le même périphérique avec le même ID et chaque passé.