EntityFramework, DbContextScope et Effort - exception: DbContext a été supprimé dans le test unitaire.

c# dbcontext effort entity-framework unit-testing

Question

J'essaie d'écrire des tests unitaires (avec NUnit) pour la couche de service qui utilise:

  1. Entity Framework en tant que couche d'accès aux données
  2. DbContextScope pour gérer la durée de vie de DbContext

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?

Réponse acceptée

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.



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi