简体   繁体   中英

Expression<Func<T, bool>> is not working as expected in mock setup

This is the method I want to test:

public async Task<List<Lesson>> GetProfessionalLessonsByTutorIdAsync(long tutorId)
{
     return await _unitOfWork.Repository<Lesson>().GetEntities(l => l.TeacherId == tutorId && l.LessonTypeId == 1)
                .AsNoTracking().ToListAsync();
}

Here GetEntities method as follows:

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

My test method:

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

Problem is when await lessonService.GetProfessionalLessonsByTutorIdAsync(1); is being called in the test method its returning 2 items, actually it should return 1 with the matched condition.

I guess problem is in the following mock set up code:

_mockUnitOfWork.Setup(uow => uow.Repository<Lesson>().GetEntities(It.IsAny<Expression<Func<Lesson, bool>>>() ,
                It.IsAny<Func<IQueryable<Lesson>, IIncludableQueryable<Lesson, object>>>())).Returns(fakeLessonList.Object);

May be I missed something! Any help from the expert please!

Note: If I modify the original method as follows then it works.

public async Task<List<Lesson>> GetProfessionalLessonsByTutorIdAsync(long tutorId)
{
     return await _unitOfWork.Repository<Lesson>().GetEntities().Where(l => l.TeacherId == tutorId && l.LessonTypeId == 1)
                .AsNoTracking().ToListAsync();
}

Now another question is why test method works for GetEntities().Where(l => l.TeacherId == tutorId && l.LessonTypeId == 1) but not works for .GetEntities(l => l.TeacherId == tutorId && l.LessonTypeId == 1) .

The problem with your setup is that you set GetEntities to always return the full fakeLessonList list. You never run in against the queries provided as arguments.

To do this, you can use another overload of the Moq Returns() method which provides the arguments passed by the caller via lambda method. Also, there's no need to mock the list if you want to actually filter out with given conditions, that is, run the queries for real.

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

I didn't test the code, but I hope it provides you the idea of what needs to be adjusted.

EDIT: Here's what's happening in your test.

  • You set up a mock list, a mock of GetEntities() and some others...
  • You call the real await lessonService .GetProfessionalLessonsByTutorIdAsync(1); method.
  • Inside the method there is a call to the mock of GetEntities(condition) but it simply ignores the condition because your mock (the way you set it up) doesn't evaluate any conditions, it always returns the full list.

In other words, there's simply no difference whether your GetProfessionalLessonsByTutorIdAsync method calls GetEntities(condition) or GetEntities() because the mock is set up to always return the full list and disregard any condition passed to it.

And if you change the code of GetProfessionalLessonsByTutorIdAsync to run GetEntities().Where(condition) you're then finally evaluating the condition against the list that GetEntities() returns. The problem with that is that you no longer have control of what's happening with the condition .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM