EntityFramework、DbContextScope 和 Effort - 异常:DbContext 已在单元测试内处理
EntityFramework, DbContextScope and Effort - exception: DbContext has been disposed inside unit test
我正在尝试为使用的服务层编写单元测试(使用 NUnit):
- Entity Framework 作为数据访问层
- DbContextScope 用于管理 DbContext 生命周期
我还使用 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
在调用时抛出异常。有没有人遇到过类似的问题,或者知道我应该怎么做才能成功测试这个和类似的场景?
对于遇到类似问题的任何人,我创建了一个有点脏但有效的解决方案(至少我希望如此)。除了我在问题中写的内容之外,我还创建了一个从真实上下文派生的 class 并使 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
在测试中过早处理的问题。
我正在尝试为使用的服务层编写单元测试(使用 NUnit):
- Entity Framework 作为数据访问层
- DbContextScope 用于管理 DbContext 生命周期
我还使用 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
在调用时抛出异常。有没有人遇到过类似的问题,或者知道我应该怎么做才能成功测试这个和类似的场景?
对于遇到类似问题的任何人,我创建了一个有点脏但有效的解决方案(至少我希望如此)。除了我在问题中写的内容之外,我还创建了一个从真实上下文派生的 class 并使 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
在测试中过早处理的问题。