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

Moq - mocking a complex repository method - list object not being returned

我正在使用 Entity Framework 并且有一个通用的存储库方法,它允许我查询 DbSet 并且还包括导航属性。我正在尝试为使用这段代码的一些代码编写单元测试,我需要模拟它以进行单元测试。我正在使用最小起订量。

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

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 和相关的 LocationRoomsStaff 集合返回一个 Location 实体。

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

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

从此处显示的最小起订量设置代码 - 我想我应该取回 1 个位置的列表 - 我指定的 ID 为 1 的位置对象。但是当我 运行 我的单元测试并点击它时代码 - FindByInclude returns 空列表的设置方法。因此,当 DeleteLocation 方法中的代码被命中并调用 Single() 方法时,我收到一个错误,即 "element contains no sequence".

我认为问题是 FindByInclude 方法的 Moq 设置的语法有问题,但不确定哪里出了问题。

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

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

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

作为@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 定义的方法。