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 和相关的 LocationRooms
和 Staff
集合返回一个 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
定义的方法。
我正在使用 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 和相关的 LocationRooms
和 Staff
集合返回一个 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
定义的方法。