How do I mock multiple levels of DbSet.Include lambdas?

5.3k Views Asked by At

I'm using Moq to write unit tests that use Entity Framework 6 DbSet and DbContext objects. I have a service method with a cascading/multi-level Include and I can't figure out how to set it up for testing. The service method looks something like this:

return DataContext.Cars
    .Include(p => p.Model)
    .Include(p => p.Model.Make)
    .Select(c => new 
         {
             Key = c.CarId, 
             Value = string.Format("{0} {1} {2}", c.Model.Make.Name, c.Model.Name, c.Trim)
         }
    ).ToArray();

I know that I have to setup the Include to return the mocked object, like this:

mockCarDbSet.Setup(m => m.Include(It.IsAny<string>())).Returns(mockCarSet.Object);

But I'm getting a null reference exception from the cascaded .Include(p => p.Model.Make). How do I set up Moq to handle multiple levels of Include?

EDIT
OK, so it turns out that I can't use It.IsAny<string> for Include calls that use lambdas instead of strings, so now I have two problems:

  1. How do I setup a mock with Include that accepts a lambda?
  2. Will the setup for above cascade to multiple levels?
2

There are 2 best solutions below

0
On BEST ANSWER

So thanks to @Old Fox reminding me that Moq won't work with static members, I found a way to do this using Microsoft Fakes. Shims allows you to shim static methods. I used Moq to set up Mock<DbSet> objects for each of the entities:

var carData = new List<Car>{new Car{ Trim = "Whatever" }};  
var mockCarSet = new Mock<DbSet<Car>>();
mockCarSet.As<IQueryable<Car>>().Setup(m => m.Provider).Returns(carData.Provider);
mockCarSet.As<IQueryable<Car>>().Setup(m => m.Expression).Returns(carData.Expression);
mockCarSet.As<IQueryable<Car>>().Setup(m => m.ElementType).Returns(carData.ElementType);
mockCarSet.As<IQueryable<Car>>().Setup(m => m.GetEnumerator()).Returns(carData.GetEnumerator);
var mockMakeSet = new Mock<DbSet<Make>>();
//do the same stuff as with Car for IQueryable Setup
var mockModelSet = new Mock<DbSet<Model>>();
//do the same stuff as with Car for IQueryable Setup
using(ShimsContext.Create())
{
    //hack to return the first, since this is all mock data anyway
    ShimModel.AllInstances.MakeGet = model => mockMakeSet.Object.First();
    ShimCar.AllInstances.ModelGet = car => mockModelSet.Object.First();
    //run the test
}
6
On

include() is a static method(extension method). Moq doesn't support a static methods mock(read this link).

To test your code you need to set your mockCarDbSet to return IQueryable<Car>:

var carQuery = new List<Car>
{
    //add cars
}
IQueryable<Post> query = carQuery.AsQueryable();

return query as a result of DataContext.Cars

Those steps will work around the static method problem.