3

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?
abatishchev
  • 98,240
  • 88
  • 296
  • 433
AJ.
  • 16,368
  • 20
  • 95
  • 150

2 Answers2

2

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.

Community
  • 1
  • 1
Old Fox
  • 8,629
  • 4
  • 34
  • 52
  • Actually, Moq handles .Include(It.IsAny) just fine, which works great against DbSet.Include("SomeEntity"). See this answer: http://stackoverflow.com/a/21979183/27457 – AJ. Jun 15 '15 at 17:48
  • The example in the link use the object method. This method gets a string as a parameter([the link method](https://msdn.microsoft.com/en-us/library/gg696717%28v=vs.113%29.aspx)). In your code, you use the extension method. – Old Fox Jun 15 '15 at 18:03
  • The slight issue here is that the first use if Include(...), is the method of the DbSet (which can be mocked) that then returns an IQueryable. The second call is therefore performed using the static Include(...) extension method for IQueryable. – Phil Sep 13 '16 at 16:16
  • @Phil , ammm... I think you should go over again on the OP code... as I wrote to the OP, a work around is to set the `DataContext.Cars` to return a list of cars.... BTW 3 months later I posted [this answer to the question: How to write this EF Mock setup code as a reusable Generic Boilerplate?](http://stackoverflow.com/a/33641349/4332059)(which is relevant to this question...) – Old Fox Sep 14 '16 at 08:48
  • @Old Fox. Thanks for the advice. It's just I've found that that answer only works when the Include method of DbSet is used, but not for the QueryableExtensions.Include method. – Phil Sep 14 '16 at 12:48
  • @Phil you welcome, actually this answer won't work when the Include method of DbSet is used... The problem that the OP faced was that; he did the correct setup for the Include method of DbSet and then null reference raised... – Old Fox Sep 14 '16 at 13:42
0

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
}
AJ.
  • 16,368
  • 20
  • 95
  • 150