Moq - 模拟复杂的存储库方法 - 未返回列表对象

时间:2017-01-26 23:00:30

标签: c# entity-framework moq

我正在使用Entity Framework并拥有一个通用的存储库方法,允许我查询DbSet并包含导航属性。我正在尝试为使用这一块代码的一些代码编写单元测试,我需要模拟它进行单元测试。我正在使用Moq。

这是存储库方法 - 它是一种允许我使用表达式进行查询并且还包括我想要的相关导航属性的方法。我在Julily Lerman的关于Pluralsight的企业课程中看到了这种模式。

public IEnumerable<TEntity> FindByInclude(Expression<Func<TEntity, bool>> predicate,
                                            params Expression<Func<TEntity, object>>[] includeProperties)
{
    var query = GetAllIncluding(includeProperties);
    IEnumerable<TEntity> results = query.Where(predicate).ToList();
    return results;
}

private IQueryable<TEntity> GetAllIncluding(params Expression<Func<TEntity, object>>[] includeProperties)
{
    IQueryable<TEntity> queryable = DbSet.AsNoTracking();

    return includeProperties.Aggregate
      (queryable, (current, includeProperty) => current.Include(includeProperty));
}

以下是我在代码中使用此方法调用的示例(我只是展示了方法的相关部分):

public ApiResult DeleteLocation(int id)
{
    var location = _locationRepository
        .FindByInclude(l => l.Id == id, l => l.LocationRegions, l => l.Pools)
        .Single();

因此,此查询会根据我传入的ID以及相关的LocationLocationRooms个集合返回单个Staff实体。

如何为FindByInclude方法设置Moq?这是我的单元测试模拟设置:

var mockLocationRepository = new Mock<ILocationRepository>();
var location = new Location {Id = 1,Name = "LocationName", LocationRooms = new List<LocationRoom>(), Staff = new List<Staff>()};
mockLocationRepository.Setup(r => r.FindByInclude(l => l.Id == It.IsAny<int>(), l => l.LocationRooms, l => l.Staff))
            .Returns(() => new List<Location> { location });

从这里显示的Moq设置代码 - 我想我应该回到1个位置的列表 - 我指定的ID为1的位置对象。但是,当我运行单元测试并点击此代码时 - 设置方法为FindByInclude返回一个空列表。因此,当DeleteLocation方法中的代码被命中并且调用Single()方法时,我收到的错误是“元素不包含序列”。

我认为问题是我使用FindByInclude方法的Moq设置语法有问题,但不确定是什么问题。

2 个答案:

答案 0 :(得分:4)

作为@Nkosi答案的替代方案,你怎么不使用Moq,而是自己实现ILocationRepository的存根实现?这背后的想法是,如果嘲弄变得难以做到,也许你不应该这样做?

public class StubLocationRepository : ILocationRepository
{
    private readonly IEnumerable<Location> _findByInclude;

    public StubLocationRepository(IEnumerable<Location> findByInclude)
    {
        _findByInclude = findByInclude;
    }

    public IEnumerable<Location> FindByInclude(
        Expression<Func<Location, bool>> predicate,
        params Expression<Func<Location, object>>[] includeProperties)
    {
        return _findByInclude;
    }
}

这很简单,因为它假设你只有一种方法。如果您有很多并且不想为每个值传递常量值,那么您可以让存根的ctor采用可选参数,这样您就只能存储所需的方法。

此外,由于ILocationRepository最有可能继承自通用接口,因此您可以使用通用存根实现来子类化以构建特定存根 - 即实现ILocationRepository定义的方法。

答案 1 :(得分:2)

评论太长,所以添加为答案

是表达式。首先尝试更通用的表达式设置,看看它是否有效。

var location = new Location {
    Id = 1,
    Name = "LocationName", 
    LocationRooms = new List<LocationRoom>(), 
    Staff = new List<Staff>()
};
mockLocationRepository
    .Setup(m => m.FindByInclude(It.IsAny<Expression<Func<TEntity, bool>>>(), It.IsAny<Expression<Func<TEntity, object>>[]>())
    .Returns(() => new List<Location> { location });