IQueryableを実装するオブジェクトをモックするために夜を過ごしました。
public interface IRepo<T> : IQueryable<T>
{
}
私が思いつくことができる最高のものは次のようなものです:
var items = new Item[] {}.AsQueryable();
var repo = new Mock<IRepo>();
repo.Setup(r => r.GetEnumerator()).Returns(items.GetEnumerator());
repo.Setup(r => r.Provider).Returns(items.Provider);
repo.Setup(r => r.ElementType).Returns(items.ElementType);
repo.Setup(r => r.Expression).Returns(items.Expression);
同じことをするもっと簡潔な方法はありますか? IRepoでプロパティ/メソッドを公開すると、IQueryableを返し、次のように単純にモックする方が簡単です。
repo.Setup(r => r.GetItems()).Returns(new Items[]{ }.AsQueryable());
しかし、これは私がやりたいことではありません=)
これは新しいことではなく、よりクリーンな方法です。また、リポジトリ自体もIQueryableであるリポジトリもあるので、同じものが必要でした。私は基本的に、私のテストプロジェクトのルートレベルで、次のような拡張メソッドにコードを挿入して、すべてのテストで使用できるようにします。
public static class MockExtensions
{
public static void SetupIQueryable<T>(this Mock<T> mock, IQueryable queryable)
where T: class, IQueryable
{
mock.Setup(r => r.GetEnumerator()).Returns(queryable.GetEnumerator());
mock.Setup(r => r.Provider).Returns(queryable.Provider);
mock.Setup(r => r.ElementType).Returns(queryable.ElementType);
mock.Setup(r => r.Expression).Returns(queryable.Expression);
}
}
いくつかのテストでこれを実行する可能性が高いため、これは基本的に再利用性を提供するだけであり、各テストで意図を明確にし、混乱を最小限に抑えます。 :)
ルーンの答えは素晴らしく、同じことをする方法を考える時間を節約できました。ちょっとした落とし穴は、IQueryableでIQueryable拡張メソッドを2回呼び出すと(例:ToList())、2回目に結果が返されないことです。これは、列挙子が最後にあり、リセットする必要があるためです。 Rhinomocksを使用して、GetEnumeratorの実装を次のように変更しました。
mock.Stub(r => r.GetEnumerator()).Do((Func<IEnumerator<T>>) (() => {
var enumerator = queryable.GetEnumerator();
enumerator.Reset();
return enumerator;
}));
誰かが時間を節約できることを願っています。
ルーンの答えが好きです。以下は、一般的なIQueryableバージョンです。
public static void SetupIQueryable<TRepository, TEntity>(this Mock<TRepository> mock, IQueryable<TEntity> queryable)
where TRepository : class, IQueryable<TEntity>
{
mock.Setup(r => r.GetEnumerator()).Returns(queryable.GetEnumerator());
mock.Setup(r => r.Provider).Returns(queryable.Provider);
mock.Setup(r => r.ElementType).Returns(queryable.ElementType);
mock.Setup(r => r.Expression).Returns(queryable.Expression);
}