EntityFramework, DbContextScope e Effort - Eccezione: DbContext è stato eliminato all'interno dell'unità test

c# dbcontext effort entity-framework unit-testing

Domanda

Sto provando a scrivere test unitari (con NUnit) per il livello di servizio che utilizza:

  1. Entity Framework come livello di accesso ai dati
  2. DbContextScope per la gestione della vita di DbContext

Io uso anche Effort.EF6 per il deridere DbContext nei test unitari. Sfortunatamente, non riesco a trovare un modo per rendere compatibile DbContextScope con Effort in modo da poter verificare correttamente tutti i casi.


Panoramica del codice

Il livello di servizio è costituito da classi (servizi) che eseguono alcune logiche di business. Ogni metodo viene considerato come una transazione completa, terminata con context.SaveChanges() . Esempio:

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

Per testare l'unità di questo metodo, faccio qualche preparazione prima di ogni 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);
    }

Il test e il problema

Ecco un semplice test unitario:

    [Test]
    public void Insert_WhenValidModelPassed_ShouldInsertNewRecord()
    {
        // Given
        BusinessModel.Department newDepartment = DataInitializer.GetExampleOfNewDepartment();

        // When
        _departmentsService.Insert(newDepartment);

        // Then
        Assert.AreEqual(3, _dbEntities.Departments.Count());
    }

Il problema è che il test fallisce con l'eccezione:

System.InvalidOperationException : The operation cannot be completed because the DbContext has been disposed.

Sembra che DbContextScope utilizzato all'interno del metodo Insert disponga internamente dei contesti assegnati alla fine del blocco using e quindi l' Assert genera un'eccezione quando viene richiamato. Qualcuno si imbatte in un problema simile o sa solo cosa dovrei fare per testare con successo questo e scenari simili?

Risposta accettata

Per chiunque incontri un problema simile, ho creato una soluzione un po 'sporca ma funzionante (almeno lo spero). Oltre a ciò che ho scritto nella domanda, ho creato una classe derivata dal contesto reale e ho fatto fare il metodo Dispose ... niente. Ho anche aggiunto un metodo RealDispose che viene chiamato alla fine di ogni 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);
    }

Forse c'è una soluzione migliore, ma per ora sembra risolvere il problema con DbContext viene smaltito troppo presto nei test.




Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché