C#の単体テスト用モックフレームワークといえば、Moqがデフォルトですが、Entityframeworkとの相性があまりよくありませんでした。
DbContextのモック実装を用意したり、実コードと差し替えるためにインターフェースを作成する必要があるので、モックの規模が膨らみ、クラス設計にも影響します。
とは言え、単体テストフェーズでJavaのDBUnitのようなDBにデータを仕込むアプローチを取ると、テストデータが膨大になったり、仕様変更、リファクタリングのコストが大きくなってしまいます。
Entityframework6以降では、モック用のインターフェースやダミーを作成せずに、単体テストが組めるようになりました。
DbContextの変更
public class MyDbContext : DbContext
{
// Mockに差し替えるEntityはvirtualにしてオーバーライド可能に
public virtual DbSet<MyEntity> MyEntitys { get; set; }
}
モックの設定(SetUp)
// Mock用のデータ
var dataEntity = new List<MyEntity>
{
new MyEntity {Id = 1, Date = Datetime.Now },
new MyEntity {Id = 2, Date = Datetime.Now }
}.AsQueryable();
// DbSetのMock
var mockMyEntity = new Mock<DbSet<MyEntity>>();
// DbSetとテスト用データを紐付け
mockMyEntity.As<IQueryable<Type>>().Setup(m => m.Provider).Returns(dataEntity.Provider);
mockMyEntity.As<IQueryable<Type>>().Setup(m => m.Expression).Returns(dataEntity.Expression);
mockMyEntity.As<IQueryable<Type>>().Setup(m => m.ElementType).Returns(dataEntity.ElementType);
mockMyEntity.As<IQueryable<Type>>().Setup(m => m.GetEnumerator()).Returns(dataEntity.GetEnumerator());
// DBContextにMockを設定
var mockContext = new Mock<<MyDbContext>();
mockContext.Setup(m => m.TestEntity).Returns(mockMyEntity.Object);
mockMyEntity.SetUp(f => f.Create()).Returns(new MyEntity());
// DBContextのMockをコンストラクタなどからテスト対象クラスに受け渡す
var target = new Target(mockContext);
エンティティの関連を使っている場合、自動では設定されないので、自力で設定する必要があります。
参照系のテスト
// Mockに設定したデータが取得できる
Assert.AreEqual(1, target.List(1).Id);
更新系のテスト
var ret = target.Save("xyz");
// Addが一回呼び出されること
mockMyEntity.Verify(m => m.Add(It.IsAny<MyEntity>()), Times.Once);
// 想定通りのエンティティが作成されていること
mockMyEntity.Verify(m => m.Add(It.Is<MyEntity>(t => t.Id.Equals(”1”))));
単体テストはそれなりに手間がかかりますが、仕様変更やリファクタリングがあっても心の平安が保てますし、デグレもないので結果的に早いです。
追記
MSDNでSQL LiteやLocalDBのInMemory Modeや使ったテスト方法が紹介されていました。
クエリのテストする場合、こちらの方がMoqに慣れていない人には分かりやすいかもしれません。