Expression <func <t,bool =“” >>在模拟设置中无法正常工作

时间:2018-12-27 07:45:27

标签: c# unit-testing entity-framework-core moq

这是我要测试的方法:

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);时,它返回2个项目,实际上它应该返回匹配条件的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)

1 个答案:

答案 0 :(得分:1)

设置的问题是您将GetEntities设置为始终返回完整的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 (进行设置)不会评估任何条件,它始终会返回完整列表。

换句话说,您的GetProfessionalLessonsByTutorIdAsync方法调用GetEntities(condition)还是GetEntities()根本没有区别,因为模拟已设置为始终返回完整列表并忽略传递给它的任何条件。

如果将GetProfessionalLessonsByTutorIdAsync的代码更改为运行GetEntities().Where(condition),则最终将根据condition返回的列表评估GetEntities()。这样做的问题是您不再控制condition发生了什么。