【问题标题】:Moq with IQueryable Extension Methods具有 IQueryable 扩展方法的起订量
【发布时间】:2015-12-02 03:32:22
【问题描述】:

总的来说,我是 Moq 框架和单元测试的新手,我正在尝试为下面的存储库功能创建一个单元测试。

存储库方法

public IQueryable<Campaign> AllIncluding(params Expression<Func<Campaign, object>>[] includeProperties)
{
    IQueryable<Campaign> query = _context.Campaigns;
    foreach (var includeProperty in includeProperties)
    {
        query = query.Include(includeProperty);
    }
    return query;
}

带起订量设置的单元测试

//Arrange
var data = new List<Models.Campaign>
{
    new Models.Campaign { Id = 1, Name = "Campaign Past", StartDate = DateTime.Now.AddDays(-10), EndDate = DateTime.Now.AddDays(-5) },
    new Models.Campaign { Id = 2, Name = "Campaign Active", StartDate = DateTime.Now.AddDays(-4), EndDate = DateTime.Now.AddDays(3) },
    new Models.Campaign { Id = 2, Name = "Campaign Future", StartDate = DateTime.Now.AddDays(4), EndDate = DateTime.Now.AddDays(10) }
}.AsQueryable();
var _moqSet = new Mock<DbSet<Models.Campaign>>();
_moqSet.As<IQueryable<Models.Campaign>>().Setup(m => m.Provider).Returns(data.Provider);
_moqSet.As<IQueryable<Models.Campaign>>().Setup(m => m.Expression).Returns(data.Expression);
_moqSet.As<IQueryable<Models.Campaign>>().Setup(m => m.ElementType).Returns(data.ElementType);
_moqSet.As<IQueryable<Models.Campaign>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
//_moqSet.As<IQueryable<Models.Campaign>>()
//    .Setup(m => m.Include(It.IsAny<Expression<Func<Models.Campaign, object>>>()))
//    .Returns((Expression<Func<Models.Campaign, object>> predicate) =>
//    {
//        return _moqSet.Object.Include(predicate);
//    });
var _moqContext = new Mock<Context.IPrizeSelectionContext>();
_moqContext.Setup(m => m.Campaigns).Returns(_moqSet.Object);
Func<IQueryable<Models.Campaign>, Expression<Func<Models.Campaign, object>>, IQueryable<Models.Campaign>> includeMethod = (query, expression) =>
{
    return query.Include(expression);
};
//Act
List<Models.Campaign> allResults = null;
using (var sut = new CampaignRepository(_moqContext.Object, includeMethod))
{
    allResults = sut.AllIncluding(o => o.Id, o => o.Name).OrderBy(o => o.Id).ToList();
}
//Assert
Assert.IsNotNull(allResults);
Assert.AreEqual(3, allResults.Count);
Assert.IsNull(allResults[0].StartDate);

query.include(includeProperty) 之后我得到一个空数据我试图模拟包含函数,但是在尝试设置模拟 DbSet 时我得到一个Expression references a method that does not belong to the mocked object: m =&gt; m.Include&lt;Campaign,Object&gt;(It.IsAny&lt;Expression'1&gt;()) 异常。经过一番挖掘,我发现 Moq 有问题或无法模拟扩展方法,请参阅Question Mocking Extionsion Methods with Moq

按照This Blog 中的步骤,我尝试执行委托模式

存储库重写

Func<IQueryable<Campaign>, Expression<Func<Campaign, object>>, IQueryable<Campaign>> _includeMethod = null;
...
public CampaignRepository(IPrizeSelectionContext context, Func<IQueryable<Campaign>, Expression<Func<Campaign, object>>, IQueryable<Campaign>> includeMethod)
    : this(context)
{
    _includeMethod = includeMethod;
}
...
public IQueryable<Campaign> AllIncluding(params Expression<Func<Campaign, object>>[] includeProperties)
{
    IQueryable<Campaign> query = _context.Campaigns;
    foreach (var includeProperty in includeProperties)
    {
        if (_includeMethod == null)
            query = query.Include(includeProperty);
        else
            query = _includeMethod(query, includeProperty);
    }
    return query;
}

这是我的单元测试设置

Func<IQueryable<Models.Campaign>, Expression<Func<Models.Campaign, object>>, IQueryable<Models.Campaign>> includeMethod = (query, expression) =>
{
    return query.Include(expression);
};

List<Models.Campaign> allResults = null;
using (var sut = new CampaignRepository(_moqContext.Object, includeMethod))
{
    allResults = sut.AllIncluding(o => o.Id, o => o.Name).OrderBy(o => o.Id).ToList();
}

不过这种模式也以同样的方式结束,IQueryable&lt;Campaign&gt; query = _context.Campaigns; 返回 Mock 类型但我不能使用扩展方法。

谁能指出正确的方向来测试我的存储库方法?

【问题讨论】:

    标签: c# linq unit-testing moq


    【解决方案1】:

    看起来您正在尝试在这里测试 2 件事: 1.包含扩展方法 2. AllIncluding方法

    我建议你将 Include 方法分离到不同的静态类中,例如:

    public static class CollectionExtensions {
    public static Include(this IQueryable<Campaign>, Expression<Func<Campaign, object>> includeProperty) {
        // put implementation here
    }}
    

    这样,您可以单独测试 Include 方法。 (无论如何,大部分逻辑似乎都在 include 方法中)

    另外,我认为您不能在 _moqSet 设置中使用 _moqSet.Object,如下所示。这可能是导致 moq 无法解析表达式的原因。

    _moqSet.As<IQueryable<Models.Campaign>>()
    .Setup(m => m.Include(It.IsAny<Expression<Func<Models.Campaign, object>>>()))
    .Returns((Expression<Func<Models.Campaign, object>> predicate) =>
    {
        return _moqSet.Object.Include(predicate);
    });
    

    【讨论】:

    • 我最终只是想测试 AllIncluding 方法。我设置了Expression&lt;Func&lt;Models.Campaign, object&gt;&gt; 的注入,试图解决query.Include(includeProperty) 总是返回null 的问题。我的猜测是因为查询的类型为Mock&lt;DbSet&gt; 而不是 IQueryable。静态类实现与注入函数有何不同?我还注释掉了模拟包含方法的尝试,我更新了我的代码以反映我当前的代码状态。
    • 我没有意识到代码使用了实体框架库中的包含扩展方法。
    • 一般我会避免直接使用 DbSet 而是使用 Repository 模式。也许你可以按照这篇文章romiller.com/2012/02/14/testing-with-a-fake-dbcontext 创建一个假的 DbSet
    • 感谢您的指导。我最终根据文章创建了一个假 DbSet。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多