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 lorsque j'essaie de supprimer le contexte créé, la base de données en mémoire persiste. Cela signifie qu'un test en implique un autre.
J'ai lu cet article nit Testing Entity Framework 7 avec le magasin de données en mémoire et j'ai essayé de configurer le contexte dans le constructeur de ma TestClass. Mais cette approche ne fonctionne pas. Lorsque j'exécute des tests séparément, tout va bien, 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 par une base de données sale de la méthode de test précédente. J'essaie d'ajouter IDispose
dans la classe de test mais la méthode DatabaseContext et DB persistent en 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"
De la documentation ,
En règle générale, EF crée un seul
IServiceProvider
pour tous les contextes d'un type donné dans un AppDomain - ce qui signifie que toutes les instances de contexte partagent la même instance de base de données InMemory. En autorisant la transmission d'un fichier, vous pouvez contrôler l'étendue de la base de données InMemory.
Au lieu de rendre la classe de test jetable et d'essayer de supprimer le contexte de données de cette façon, créez-en 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 test, renouvelez un contexte de données en utilisant 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 parfaitement propre pour chaque test.
Je pense que la réponse que Nate a donnée est peut-être dépassée maintenant ou peut-être que je fais quelque chose de mal. UseInMemoryDatabase()
requiert désormais 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 au profit de l'utilisation du constructeur et éliminé qui sont appelées une fois pour chaque cas de test.
Il y a quelques lignes de débogage à partir de 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.
[Fact]
public void LocationExists_True()
{
Assert.True(_repository.LocationExists(_dbFixture.GoodLocationId));
}
J'ai également fait 8 cas de test qui ont tenté de supprimer le même appareil avec le même identifiant et chacun a réussi.