如何模拟IQueryable<T>?

42

我正在创建一个暴露IQueryable的仓库。在进行单元测试时,最好的方法是如何模拟它?

由于我在使用RhinoMocks来进行其他模拟对象的操作,因此我尝试了以下方法:

IQueryable<MyObject> QueryObject = 
    MockRepository.GenerateStub<IQueryable<MyObject>>();

这种方法不起作用,所以我尝试了这个:
IQueryable<MyObject> QueryObject = 
    (new List<MyObject> { new MyObject() }).AsQueryable();

有没有更好的方法来做这件事,或者其他模拟框架是否支持IQueryable?

我的存储库接口看起来像这样:

public interface IRepository<T> where T : TableServiceEntity
{
    IQueryable<T> Table { get; }
    void Attach(T existingItem);
    void Delete(T itemToDelete);
    void Insert(T newItem);
    T Load(string partitionKey, string rowKey);
    IEnumerable<T> Load(string partitionKey);
    IEnumerable<T> Query(IQueryable<T> query);
    IEnumerable<T> Last(int count);
    T Last();
    void Update(T item);
}

这是我想要测试的方法:

public Post LoadPost(int year, int month, int day, string slug)
{
    var query = from p in _blogRepository.Table
                where 
                    p.PartitionKey == Key.Partition(year, month, day) 
                    && p.Slug == slug
                select p;

    var posts = _blogRepository.Query(query.Take(1));

    return posts.First();
}

现在我有一个测试,将测试LoadPost功能。

[Fact]
public void LoadWillRetrieveByPartitionKeyAndRowKeyWhenUsingUriFormat()
{
    Repository
        .Stub(x => x.Query(Arg<IQueryable<Post>>.Is.Anything))
        .Return(new List<Post> {_post});

    var result = Service.LoadPost(
                            _post.Year(),
                            _post.Month(), 
                            _post.Day(), 
                            _post.Slug);

    Assert.NotNull(result);
}

这段代码来自我的AzureBlog项目。

4个回答

10

我通常会像你在测试中所做的那样。在编写我的测试时,我假设 .Net 库类能够正常工作且不存在错误,以便在测试中使用它们。当我需要测试列表、集合、可查询对象、字典等内容时,我只需创建真实的对象并填充测试数据即可。这样可以使测试更易于阅读和编写,并且老实说风险几乎为零。


1
提供单元测试的代码示例。 - Edward Olamisan

5

如果您想模拟您的存储库,您不应该模拟IQueryable。相反,您可以模拟存储库的方法来返回固定的、已知的值(如第二个示例),这些值可以用于运行单元测试。


我曾经尝试过这种方式,但最终我创建了一个特定存储库的额外层来覆盖通用存储库。这导致我的领域服务 -> 领域存储库 -> 通用存储库。如果我能够从领域服务 -> 通用存储库,并且仍然能够进行测试而不会泄露实现细节到领域服务中,那么我会更加满意,因为需要维护和测试的代码将变少。 - Aaron Weiker
我明白你的意思。但是,如果你的问题源于为DomainService编写单元测试,那么我认为你应该模拟DomainRepository,不必担心通用存储库(暂时)。通过模拟其依赖项来进行单元测试是最好的方法。就DomainService而言,它不应关心DomainRepository的实现方式(即继承自基类)。希望这可以帮到你! - PatrickSteele

2

我知道这是一个老问题,但只想加入我的两分钱。

我曾经遇到过与SharpLite生成的存储库相同的问题,这是我偶尔使用的ASP .NET MVC框架。经过一段时间的研究,我找到了一个解决方案,唯一的问题是它使用Moq而不是Rhino Mocks,但可能你可以找到一种适应它的方法。我在这里写了一篇博客文章,介绍如何实现。

基本上是创建一个实现IQueryable接口的列表,并将其用作虚假数据背景。希望我能帮到你!


1
请勿提供任何链接而没有提供示例...您的博客已经消失了。 - UNeverNo
1
没有任何辩解!那是很久以前的事情了,虽然我大约5-6年前停止使用.NET,但我想事情已经发生了变化,这可能不再是最佳实践。 - David Conde
现在我们永远不会知道了。好吧,你会知道的。 - Red
https://web.archive.org/web/20180715001608/http://davidcondemarin.blogspot.com/2012/07/testing-sarp-lite-repositories-with-moq.html - Rolf Kristensen

0

我不确定这是否能帮到你,但我做过类似于你所说的事情。在我的场景中,我有一个使用存储库的数据上下文类。

我首先创建了一个包含IQueryable方法的接口(IRepository)。然后我创建了两个实现此接口的类。一个类使用ORM进行数据操作(DbEntityRepository),另一个类使用类属性(MemoryRepository)。数据上下文类有一个需要IRepository的构造函数。通过这样做,我可以在测试数据上下文时使用MemoryRepository,而在应用程序中可以使用DbEntityRepository。

如果你感兴趣,你可以在codeplex上找到代码:IQToolkitContrib


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接