EntityFramework,DbContextScope和Effort - 异常:DbContext已被置于单元测试中

时间:2017-04-18 21:22:05

标签: c# entity-framework unit-testing dbcontext effort

我正在尝试使用以下服务层编写单元测试(使用NUnit):

  1. 实体框架作为数据访问层
  2. DbContextScope用于管理DbContext生存期
  3. 我还使用Effort.EF6在单元测试中模拟DbContext。不幸的是,我找不到让DbContextScope与Effort兼容的方法,以便我可以正确测试所有案例。

    代码概述

    服务层由执行某些业务逻辑的类(服务)组成。每个方法都被视为一个完整的事务,以context.SaveChanges()结束。例如:

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

    为了对这种方法进行单元测试,我会在每次测试前做一些准备工作:

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

    测试和问题

    这是一个简单的单元测试:

        [Test]
        public void Insert_WhenValidModelPassed_ShouldInsertNewRecord()
        {
            // Given
            BusinessModel.Department newDepartment = DataInitializer.GetExampleOfNewDepartment();
    
            // When
            _departmentsService.Insert(newDepartment);
    
            // Then
            Assert.AreEqual(3, _dbEntities.Departments.Count());
        }
    

    问题是测试失败,但有例外:

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

    似乎在Insert方法内部使用的DbContextScope在using块的末尾内部处理分配的上下文,因此Assert在调用时抛出异常。有没有人遇到过类似的问题,或者只是知道我应该怎样做才能成功地测试这个和类似的场景?

1 个答案:

答案 0 :(得分:5)

对于遇到类似问题的任何人,我已经创建了一个有点脏但工作的解决方案(至少我希望如此)。除了我在问题中写的内容之外,我创建了一个派生自真实上下文的类,并使Dispose方法做了......没有。我还添加了一个RealDispose方法,该方法在每次测试结束时调用。

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

也许有一个更好的解决方案,但目前似乎解决了DbContext在测试中过早处置的问题。