J'essaie d'écrire des tests unitaires (avec NUnit) pour la couche de service qui utilise:
J'utilise aussi Effort.EF6 pour me moquer de DbContext dans les tests unitaires. Malheureusement, je ne trouve pas le moyen de rendre DbContextScope compatible avec Effort afin de pouvoir tester correctement tous les cas.
Vue d'ensemble du code
La couche service est constituée de classes (services) qui exécutent une logique métier. Chaque méthode est traitée comme une transaction complète, terminée par context.SaveChanges()
. Exemple:
private IDbContextScopeFactory _dbContextScopeFactory;
public DepartmentsService(IDbContextScopeFactory dbContextScopeFactory)
{
_dbContextScopeFactory = dbContextScopeFactory;
}
public BusinessModel.Department Insert(BusinessModel.Department department)
{
using (var dbContextScope = _dbContextScopeFactory.Create())
{
// Validation
ValidateAndThrowOnFailure(department, new DepartmentAddValidator());
// Operation
DBModel.Department newDepartment = Mapper.Map<DBModel.Department>(department);
newDepartment.InsertDateUTC = DateTime.UtcNow;
dbContextScope.DbContexts.Get<DPSContext>().Departments.Add(newDepartment);
dbContextScope.SaveChanges();
return Mapper.Map<BusinessModel.Department>(newDepartment);
}
}
Pour tester une telle méthode, je fais une préparation avant chaque test:
private IDepartmentsService _departmentsService;
private IDbContextScopeFactory _dbContextScopeFactory;
private IDbContextFactory _dbContextFactory;
private DBModel.DPSContext _dbEntities;
[SetUp]
public void ReInitializeTest()
{
// Setup DbContext with Effort.EF6
string connStr = ConfigurationManager.ConnectionStrings["DPSContext"].ConnectionString;
DbConnection connection = EntityConnectionFactory.CreateTransient(connStr);
_dbEntities = new DBModel.DPSContext(connection);
// Fill DbContext with in-memory data
_dbEntities.Departments.AddRange(DataInitializer.GetDepartments());
_dbEntities.SaveChanges();
// Mock IDbContextFactory so that it returns in-memory context
var contextFactoryMock = new Mock<IDbContextFactory>();
contextFactoryMock
.Setup(f => f.CreateDbContext<DBModel.DPSContext>())
.Returns(_dbEntities);
_dbContextFactory = contextFactoryMock.Object;
// Setup DbContextScopeFactory to use mocked context
_dbContextScopeFactory = new DbContextScopeFactory(_dbContextFactory);
_departmentsService = new DepartmentsService(_dbContextScopeFactory);
}
Le test et le problème
Voici un test unitaire simple:
[Test]
public void Insert_WhenValidModelPassed_ShouldInsertNewRecord()
{
// Given
BusinessModel.Department newDepartment = DataInitializer.GetExampleOfNewDepartment();
// When
_departmentsService.Insert(newDepartment);
// Then
Assert.AreEqual(3, _dbEntities.Departments.Count());
}
Le problème est que le test échoue avec une exception:
System.InvalidOperationException : The operation cannot be completed because the DbContext has been disposed.
Il semble que DbContextScope, utilisé dans la méthode Insert
, dispose en interne des contextes attribués à la fin du bloc using
, de sorte que l' Assert
lève une exception lorsqu'il est appelé. Quelqu'un at-il rencontré un problème similaire ou sait-il simplement ce que je devrais faire pour tester avec succès ce scénario et des scénarios similaires?
Pour ceux qui rencontrent un problème similaire, j'ai créé une solution un peu sale mais qui fonctionne (du moins je l'espère). En plus de ce que j'ai écrit dans la question, j'ai créé une classe dérivée du contexte réel et fait en sorte que la méthode Dispose
ne fasse ... rien. J'ai également ajouté une méthode RealDispose
appelée à la fin de chaque test.
public class TestableDPSContext : DBModel.DPSContext
{
public TestableDPSContext(DbConnection connection)
: base(connection)
{
}
protected override void Dispose(bool disposing)
{
// Do nothing
}
public void RealDispose(bool disposing)
{
// Invoke real dispose
base.Dispose(disposing);
}
}
[TearDown]
public void FinishTest()
{
_dbEntities.RealDispose(false);
}
Peut-être y a-t-il une meilleure solution, mais pour l’instant, cela semble résoudre le problème, DbContext
étant disposé trop tôt dans les tests.