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.

发生的事情