Expression<Func<T, bool>> 在模拟设置中未按预期工作
Expression<Func<T, bool>> is not working as expected in mock setup
这是我要测试的方法:
public async Task<List<Lesson>> GetProfessionalLessonsByTutorIdAsync(long tutorId)
{
return await _unitOfWork.Repository<Lesson>().GetEntities(l => l.TeacherId == tutorId && l.LessonTypeId == 1)
.AsNoTracking().ToListAsync();
}
此处GetEntities
方法如下:
public IQueryable<TEntity> GetEntities(Expression<Func<TEntity, bool>> condition = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null)
{
IQueryable<TEntity> query = _dbSet;
if (condition != null)
{
query = query.Where(condition);
}
if (include != null)
{
query = include(query);
}
return query;
}
我的测试方法:
[Fact]
public async Task GetProfessionalLessonsByTutorIdAsync_WithTutorIdInputParam_ReturnsListOfLesson()
{
// Arrange
private readonly Mock<IUnitOfWork> _mockUnitOfWork = new Mock<IUnitOfWork>();
private readonly Mock<IHttpContextAccessor> _mockContextAccessor = new Mock<IHttpContextAccessor>();
private readonly Mock<IUserService> _mockUserService = new Mock<IUserService>();
var fakeLessonList = new List<Lesson>
{
new Lesson() { LessonId = 1, LessonTypeId = 1,LanguageId = 1, TeacherId = 1, LessonName = "Professional Lesson"},
new Lesson() { LessonId = 2,LessonTypeId = 2, LanguageId = 2, TeacherId = 2, LessonName = "Professional Lesson"}
}.AsQueryable().BuildMock();
_mockUnitOfWork.Setup(uow => uow.Repository<Lesson>().GetEntities(It.IsAny<Expression<Func<Lesson, bool>>>() ,
It.IsAny<Func<IQueryable<Lesson>, IIncludableQueryable<Lesson, object>>>())).Returns(fakeLessonList.Object);
LessonService lessonService = new LessonService(_mockUnitOfWork.Object, _mockContextAccessor.Object, _mockUserService.Object);
// Act
var exceptedValue = 1;
List<Lesson> lessons = await lessonService .GetProfessionalLessonsByTutorIdAsync(1);
var actualValue = lessons.Count; // Here count should be 1 but its getting 2
//Assert
Assert.Equal(exceptedValue, actualValue);
}
问题是在测试方法中调用 await lessonService.GetProfessionalLessonsByTutorIdAsync(1);
时 returning 2 项,实际上它应该 return 1 具有匹配条件。
我猜问题出在以下模拟设置代码中:
_mockUnitOfWork.Setup(uow => uow.Repository<Lesson>().GetEntities(It.IsAny<Expression<Func<Lesson, bool>>>() ,
It.IsAny<Func<IQueryable<Lesson>, IIncludableQueryable<Lesson, object>>>())).Returns(fakeLessonList.Object);
可能是我漏掉了什么!请专家帮忙!
注意:如果我将原来的方法修改如下就可以了。
public async Task<List<Lesson>> GetProfessionalLessonsByTutorIdAsync(long tutorId)
{
return await _unitOfWork.Repository<Lesson>().GetEntities().Where(l => l.TeacherId == tutorId && l.LessonTypeId == 1)
.AsNoTracking().ToListAsync();
}
现在另一个问题是为什么测试方法适用于 GetEntities().Where(l => l.TeacherId == tutorId && l.LessonTypeId == 1)
但不适用于 .GetEntities(l => l.TeacherId == tutorId && l.LessonTypeId == 1)
。
您设置的问题是您将 GetEntities
设置为始终 return 完整的 fakeLessonList
列表。您永远不会 运行 反对作为参数提供的查询。
为此,您可以使用 Moq Returns()
方法的另一个重载,它提供调用者通过 lambda 方法传递的参数。此外,如果您想实际过滤掉给定条件,则无需模拟列表,即 运行 真实查询。
var fakeLessonList = new List<Lesson>
{
new Lesson() { LessonId = 1, LessonTypeId = 1,LanguageId = 1, TeacherId = 1, LessonName = "Professional Lesson"},
new Lesson() { LessonId = 2,LessonTypeId = 2, LanguageId = 2, TeacherId = 2, LessonName = "Professional Lesson"}
}.AsQueryable(); // .BuildMock(); - no mock, just a real list
_mockUnitOfWork.Setup(uow => uow.Repository<Lesson>().GetEntities(It.IsAny<Expression<Func<Lesson, bool>>>(),
It.IsAny<Func<IQueryable<Lesson>, IIncludableQueryable<Lesson, object>>>()))
.Returns(
(Expression<Func<Lesson, bool>> condition,
Func<IQueryable<Lesson>, IIncludableQueryable<Lesson, object>> include) =>
// Run the queries against the list
// Need to add some checks in case any of those are null
fakeLessonList.Where(condition)
);
我没有测试代码,但我希望它能为您提供需要调整的想法。
编辑:
这是您的测试中发生的事情。
- 您设置了一个模拟列表,
GetEntities()
和其他一些的模拟...
- 你调用了真正的
await lessonService .GetProfessionalLessonsByTutorIdAsync(1);
方法。
- 方法内部调用了
GetEntities(condition)
的 mock 但它只是忽略了 condition
因为你的 mock(您设置它的方式)不评估任何条件,它总是 return 是完整列表。
换句话说,无论您的 GetProfessionalLessonsByTutorIdAsync
方法调用 GetEntities(condition)
还是 GetEntities()
都没有区别,因为 mock 设置为始终 return 完整列表并忽略传递给它的任何条件。
如果您将 GetProfessionalLessonsByTutorIdAsync
的代码更改为 运行 GetEntities().Where(condition)
,那么您最终会根据 GetEntities()
[的列表评估 condition
return秒。这样做的问题是您无法再控制 condition
.
发生的事情
这是我要测试的方法:
public async Task<List<Lesson>> GetProfessionalLessonsByTutorIdAsync(long tutorId)
{
return await _unitOfWork.Repository<Lesson>().GetEntities(l => l.TeacherId == tutorId && l.LessonTypeId == 1)
.AsNoTracking().ToListAsync();
}
此处GetEntities
方法如下:
public IQueryable<TEntity> GetEntities(Expression<Func<TEntity, bool>> condition = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null)
{
IQueryable<TEntity> query = _dbSet;
if (condition != null)
{
query = query.Where(condition);
}
if (include != null)
{
query = include(query);
}
return query;
}
我的测试方法:
[Fact]
public async Task GetProfessionalLessonsByTutorIdAsync_WithTutorIdInputParam_ReturnsListOfLesson()
{
// Arrange
private readonly Mock<IUnitOfWork> _mockUnitOfWork = new Mock<IUnitOfWork>();
private readonly Mock<IHttpContextAccessor> _mockContextAccessor = new Mock<IHttpContextAccessor>();
private readonly Mock<IUserService> _mockUserService = new Mock<IUserService>();
var fakeLessonList = new List<Lesson>
{
new Lesson() { LessonId = 1, LessonTypeId = 1,LanguageId = 1, TeacherId = 1, LessonName = "Professional Lesson"},
new Lesson() { LessonId = 2,LessonTypeId = 2, LanguageId = 2, TeacherId = 2, LessonName = "Professional Lesson"}
}.AsQueryable().BuildMock();
_mockUnitOfWork.Setup(uow => uow.Repository<Lesson>().GetEntities(It.IsAny<Expression<Func<Lesson, bool>>>() ,
It.IsAny<Func<IQueryable<Lesson>, IIncludableQueryable<Lesson, object>>>())).Returns(fakeLessonList.Object);
LessonService lessonService = new LessonService(_mockUnitOfWork.Object, _mockContextAccessor.Object, _mockUserService.Object);
// Act
var exceptedValue = 1;
List<Lesson> lessons = await lessonService .GetProfessionalLessonsByTutorIdAsync(1);
var actualValue = lessons.Count; // Here count should be 1 but its getting 2
//Assert
Assert.Equal(exceptedValue, actualValue);
}
问题是在测试方法中调用 await lessonService.GetProfessionalLessonsByTutorIdAsync(1);
时 returning 2 项,实际上它应该 return 1 具有匹配条件。
我猜问题出在以下模拟设置代码中:
_mockUnitOfWork.Setup(uow => uow.Repository<Lesson>().GetEntities(It.IsAny<Expression<Func<Lesson, bool>>>() ,
It.IsAny<Func<IQueryable<Lesson>, IIncludableQueryable<Lesson, object>>>())).Returns(fakeLessonList.Object);
可能是我漏掉了什么!请专家帮忙!
注意:如果我将原来的方法修改如下就可以了。
public async Task<List<Lesson>> GetProfessionalLessonsByTutorIdAsync(long tutorId)
{
return await _unitOfWork.Repository<Lesson>().GetEntities().Where(l => l.TeacherId == tutorId && l.LessonTypeId == 1)
.AsNoTracking().ToListAsync();
}
现在另一个问题是为什么测试方法适用于 GetEntities().Where(l => l.TeacherId == tutorId && l.LessonTypeId == 1)
但不适用于 .GetEntities(l => l.TeacherId == tutorId && l.LessonTypeId == 1)
。
您设置的问题是您将 GetEntities
设置为始终 return 完整的 fakeLessonList
列表。您永远不会 运行 反对作为参数提供的查询。
为此,您可以使用 Moq Returns()
方法的另一个重载,它提供调用者通过 lambda 方法传递的参数。此外,如果您想实际过滤掉给定条件,则无需模拟列表,即 运行 真实查询。
var fakeLessonList = new List<Lesson>
{
new Lesson() { LessonId = 1, LessonTypeId = 1,LanguageId = 1, TeacherId = 1, LessonName = "Professional Lesson"},
new Lesson() { LessonId = 2,LessonTypeId = 2, LanguageId = 2, TeacherId = 2, LessonName = "Professional Lesson"}
}.AsQueryable(); // .BuildMock(); - no mock, just a real list
_mockUnitOfWork.Setup(uow => uow.Repository<Lesson>().GetEntities(It.IsAny<Expression<Func<Lesson, bool>>>(),
It.IsAny<Func<IQueryable<Lesson>, IIncludableQueryable<Lesson, object>>>()))
.Returns(
(Expression<Func<Lesson, bool>> condition,
Func<IQueryable<Lesson>, IIncludableQueryable<Lesson, object>> include) =>
// Run the queries against the list
// Need to add some checks in case any of those are null
fakeLessonList.Where(condition)
);
我没有测试代码,但我希望它能为您提供需要调整的想法。
编辑: 这是您的测试中发生的事情。
- 您设置了一个模拟列表,
GetEntities()
和其他一些的模拟... - 你调用了真正的
await lessonService .GetProfessionalLessonsByTutorIdAsync(1);
方法。 - 方法内部调用了
GetEntities(condition)
的 mock 但它只是忽略了condition
因为你的 mock(您设置它的方式)不评估任何条件,它总是 return 是完整列表。
换句话说,无论您的 GetProfessionalLessonsByTutorIdAsync
方法调用 GetEntities(condition)
还是 GetEntities()
都没有区别,因为 mock 设置为始终 return 完整列表并忽略传递给它的任何条件。
如果您将 GetProfessionalLessonsByTutorIdAsync
的代码更改为 运行 GetEntities().Where(condition)
,那么您最终会根据 GetEntities()
[的列表评估 condition
return秒。这样做的问题是您无法再控制 condition
.