简体   繁体   中英

Moq - mocking a complex repository method - list object not being returned

I am using Entity Framework and have a generic repository method that allows me to query a DbSet and also include navigation properties. I am trying to write a unit test for some code that uses this chunk of code and I need to mock it for a unit test. I am using Moq.

Here is the repository method - it is a method that allows me to query using an expression and also including the relevant navigation properties that I want. I saw this pattern in Julie Lerman's EF in the Enterprise course on Pluralsight.

public IEnumerable<TEntity> FindByInclude(Expression<Func<TEntity, bool>> predicate,
                                            params Expression<Func<TEntity, object>>[] includeProperties)
{
    var query = GetAllIncluding(includeProperties);
    IEnumerable<TEntity> results = query.Where(predicate).ToList();
    return results;
}

private IQueryable<TEntity> GetAllIncluding(params Expression<Func<TEntity, object>>[] includeProperties)
{
    IQueryable<TEntity> queryable = DbSet.AsNoTracking();

    return includeProperties.Aggregate
      (queryable, (current, includeProperty) => current.Include(includeProperty));
}

Here is an example of how I am calling using this method in my code ( I am just showing the relevant part of the method):

public ApiResult DeleteLocation(int id)
{
    var location = _locationRepository
        .FindByInclude(l => l.Id == id, l => l.LocationRegions, l => l.Pools)
        .Single();

So this query would bring back a Single Location entity by the id I have passed in and the related LocationRooms and Staff collections.

How do I set up Moq for the FindByInclude method? Here is what I have for my unit test mock setup:

var mockLocationRepository = new Mock<ILocationRepository>();
var location = new Location {Id = 1,Name = "LocationName", LocationRooms = new List<LocationRoom>(), Staff = new List<Staff>()};
mockLocationRepository.Setup(r => r.FindByInclude(l => l.Id == It.IsAny<int>(), l => l.LocationRooms, l => l.Staff))
            .Returns(() => new List<Location> { location });

From the Moq setup code shown here - I think I should be getting back a list of 1 location - the location object I specified with Id of 1. However when I run my unit test and hit this code - the setup method for FindByInclude returns an empty list. And therefore when the code in the DeleteLocation method gets hit and the Single() method is called I am getting an error that the "element contains no sequence".

I think the problem is I have something wrong with the syntax with the Moq Setup for the FindByInclude method but not sure what is wrong.

As an alternative to @Nkosi's answer, how about you don't use Moq but implement yourself a stub implementation of ILocationRepository ? The idea behind that is that if mocking becomes hard to do, maybe you shouldn't do it?

public class StubLocationRepository : ILocationRepository
{
    private readonly IEnumerable<Location> _findByInclude;

    public StubLocationRepository(IEnumerable<Location> findByInclude)
    {
        _findByInclude = findByInclude;
    }

    public IEnumerable<Location> FindByInclude(
        Expression<Func<Location, bool>> predicate,
        params Expression<Func<Location, object>>[] includeProperties)
    {
        return _findByInclude;
    }
}

That's simplistic because it assumes you only have one method. If you have a lot and don't want to pass constant values for each of them, you could have the ctor of the stub take optional parameters so you only stub the desired methods.

Also, since ILocationRepository most likely inherits from a generic interface, you could have a generic stub implementation that you subclass to build specific stubs - ie to implement the methods that ILocationRepository defines.

Too long for a comment so adding as an answer

it is the expressions. Try a more general expression setup first and see if it works.

var location = new Location {
    Id = 1,
    Name = "LocationName", 
    LocationRooms = new List<LocationRoom>(), 
    Staff = new List<Staff>()
};
mockLocationRepository
    .Setup(m => m.FindByInclude(It.IsAny<Expression<Func<TEntity, bool>>>(), It.IsAny<Expression<Func<TEntity, object>>[]>())
    .Returns(() => new List<Location> { location });

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