EntityFramework, DbContextScope und Effort - Ausnahme: DbContext wurde im Komponententest entfernt

c# dbcontext effort entity-framework unit-testing

Frage

Ich versuche Unit-Tests (mit NUnit) für Service-Layer zu schreiben, die Folgendes verwenden:

  1. Entity Framework als Datenzugriffsschicht
  2. DbContextScope zum Verwalten der DbContext-Lebensdauer

Ich verwende auch Effort.EF6, um DbContext in Unit Tests zu verspotten. Leider kann ich keinen Weg finden, DbContextScope kompatibel mit Effort zu machen, so dass ich alle Fälle korrekt testen kann.


Überblick über den Code

Service-Layer bestehen aus Klassen (Services), die eine gewisse Geschäftslogik haben. Jede Methode wird als vollständige Transaktion behandelt und mit context.SaveChanges() . Beispiel:

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

Um diese Methode zu testen, mache ich einige Vorbereitungen vor jedem 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);
    }

Der Test und das Problem

Hier ist ein einfacher Komponententest:

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

        // When
        _departmentsService.Insert(newDepartment);

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

Das Problem ist, dass der Test mit Ausnahme fehlschlägt:

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

Es scheint, dass DbContextScope, das innerhalb der Insert Methode verwendet wird, intern zugewiesene Kontexte am Ende des using Blocks zur Verfügung stellt und somit die Assert Ausnahme Assert wenn sie aufgerufen wird. Ist jemand auf ein ähnliches Problem gestoßen oder weiß ich, was ich tun soll, um dieses und ähnliche Szenarien erfolgreich zu testen?

Akzeptierte Antwort

Für jeden, der auf ein ähnliches Problem stößt, habe ich eine etwas schmutzige aber funktionierende Lösung geschaffen (zumindest hoffe ich es). Zusätzlich zu dem, was ich in der Frage geschrieben habe, habe ich eine Klasse erstellt, die vom realen Kontext abgeleitet ist und die Dispose Methode dazu gebracht hat, nichts zu tun. Ich habe auch eine RealDispose Methode hinzugefügt, die am Ende jedes Tests aufgerufen wird.

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

Vielleicht gibt es eine bessere Lösung, aber im DbContext scheint es das Problem zu DbContext dass DbContext zu früh in Tests entsorgt wird.



Related

Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum