用Moq实现IQueryable mock类

本文关键字:mock IQueryable 实现 Moq | 更新日期: 2023-09-27 18:05:33

我花了一个晚上的时间来模拟一个实现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和简单的mock,像这样:

repo.Setup(r => r.GetItems()).Returns(new Items[]{ }.AsQueryable());

但这不是我想做的=)

用Moq实现IQueryable mock类

这不是什么新鲜事,只是一种更简洁的方法。我还有一些储存库,其中储存库本身也是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);
    }
}

这基本上只是提供了可重用性,因为您可能希望在几个测试中这样做,并且在每个测试中,它都使意图清晰,混乱最小化。:)

Rune的答案很棒,节省了我思考如何做同样的事情的时间。一个小问题是,如果你在IQueryable上调用一些IQueryable扩展方法两次(例如ToList()),那么第二次你将得不到任何结果。这是因为枚举数位于末尾,需要重置。使用Rhinomocks,我将GetEnumerator的实现更改为:

mock.Stub(r => r.GetEnumerator()).Do((Func<IEnumerator<T>>) (() => { 
    var enumerator = queryable.GetEnumerator();
    enumerator.Reset();
    return enumerator;
}));

希望这能为大家节省一些时间。

我喜欢Rune的答案。这是一个通用的iquerable版本:

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);
}

我认为这是Moq所能做到的最好的。我认为一个更可读的选择是滚动你自己的FakeRepo<T>,从System.Linq.EnumerableQuery<T>派生:

public class FakeRepo<T> : EnumerableQuery<T>, IRepo<T>
{
    public FakeRepo(IEnumerable<T> items) : base(items) { }
}

Update:您可以通过模拟EnumerableQuery<T>然后使用As<T>():

来实现这一点。
var items = new Item[0];
var repo = new Mock<EnumerableQuery<Item>(items).As<IRepo>();

我也有同样的问题。我通过修改

一行来修复它
mock.Setup(r => r.GetEnumerator()).Returns(queryable.GetEnumerator());

mock.Setup(r => r.GetEnumerator()).Returns(queryable.GetEnumerator);

我希望这里不需要额外的注释