我正在嘗試為服務層編寫單元測試(使用NUnit),該服務層使用:
我還使用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
在調用時拋出異常。有沒有人遇到類似的問題,或者只是知道我應該做些什麼來成功測試這個和類似的場景?
對於遇到類似問題的人,我創建了一個有點臟但工作的解決方案(至少我希望如此)。除了我在問題中寫的內容之外,我創建了一個派生自真實上下文的類,並使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
在測試中處理得太早的問題。