私はサービス層のためのユニットテスト(NUnitと一緒に)を書こうとしています:
私はユニットテストでDbContextを嘲笑するためにEffort.EF6を使います。残念ながら、すべてのケースを正しくテストできるように、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
がテストで早すぎるDbContext
に配置されているという問題を解決するようです。