Expression> is not working as expected in mock setup

c# entity-framework-core moq unit-testing

Question

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

1
1
12/27/2018 10:41:08 AM

Accepted Answer

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.

1
12/27/2018 8:27:51 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow