I am working with .NET 4.5, EF6 and I'm trying to use JustMock 2.0 to test my application.

I am trying to mock my database by mocking my DbContext subclass: CoreDataRepositoryContext.

To do it, I need to mock the member SaveChanges of DbContext and every DbSet typed properties of my class CoreDataRepositoryContext by returning a fake data collection. I also need to mock the following DbSet's members:

  • Add
  • Remove
  • AsQueryable

I need to mock it for all instances of CoreDataRepositoryContext and DbSet

For example, I have entities of type Order in database (table Orders) I did the following to mock the table Orders:

// FakeOrders is a list of orders (List<Order>)

var mockedContext = Mock.Create<CoreDataRepositoryContext>();

   // Mock works
Mock.Arrange(() => mockedContext.SaveChanges()).IgnoreInstance().DoNothing();
   // Mock works
Mock.Arrange(() => mockedContext.Orders).IgnoreInstance().ReturnsCollection(FakeOrders);
   // Mock works
Mock.Arrange(() => mockedContext.Orders.Add(Arg.IsAny<Order>())).IgnoreInstance().DoInstead((Order o) => FakeOrders.Add(o));
   // Mock works
Mock.Arrange(() => mockedContext.Orders.Remove(Arg.IsAny<Order>())).IgnoreInstance().DoInstead((Order o) => FakeOrders.Remove(o));
   // Mock DOES NOT work !
Mock.Arrange(() => mockedContext.Orders.AsQueryable()).IgnoreInstance().Returns(() => FakeOrders.AsQueryable());

mockedContext.Orders is of type DbSet< Order > and FakeOrders is of type List< Order >. Both classes implement the interface IEnumerable< Order >.

Mocking Add and Remove members work well because neither of the two methods is declared in the interface IEnumerable< T >.

On the other hand, AsQueryable is declared in this interface and defined by Queryable. So, as I mock the member using IgnoreInstance, calling AsQueryable from an instance of any class which implements IEnumerable< T > launches a never ending loop. Because IEnumerable< Order >.AsQueryable is mocked by FakeOrders.AsQueryable which is mocked by... FakeOrders.AsQueryable... infinite loop...

var query = mockedContext.Orders.AsQueryable(); // Infinite loop
query = FakeOrders.AsQueryable(); // Infinite loop
query = new List<Order>().AsQueryable(); // Infinite loop

How can I do to only mock DbSet< Order >.AsQueryable specifically, without mocking IEnumerable< Order >.AsQueryable using IgnoreInstance?

Thank you for your help :)

2

There are 2 answers

1
Valone On BEST ANSWER

Ok I have finally found a workaround:

I don't arrange DbContext with IgnoreInstance but I arrange the constructor of my DbContext subclass when called with a specific connection string as argument:

string myConnectionString = "CoreDBTestConnection";

// Arrange all CoreDataRepositoryContext instances for this connection string
Mock.Arrange(() => new CoreDataRepositoryContext(myConnectionString).Returns(GetMockContext());

The constructor is replaced by GetMockContext() which retruns a mocked context:

private static CoreDataRepositoryContext GetMockContext()
{
        CoreDataRepositoryContext mockContext = new CoreDataRepositoryContext();

        Mock.Arrange(() => mockContext.SaveChanges()).DoNothing();

        Mock.Arrange(() => mockContext.Set<Order>()).ReturnsCollection(FakeOrders);
        Mock.Arrange(() => mockContext.Orders).ReturnsCollection(FakeOrders);
        Mock.Arrange(() => mockContext.Orders.Add(Arg.IsAny<Order>())).DoInstead((Order o) => FakeOrders.Add(o));
        Mock.Arrange(() => mockContext.Orders.Remove(Arg.IsAny<Order>())).DoInstead((Order o) => FakeOrders.Remove(o));
        Mock.Arrange(() => mockContext.Orders.Find(Arg.IsAny<object[]>())).Returns((object[] param) => FakeOrders.Find(x => x.Id == (Guid)param[0]));
        Mock.Arrange(() => mockContext.Orders.AsQueryable()).Returns(() => FakeOrders.AsQueryable());

        // Then arrange all DbSets...

        return mockContext;
}

public static FakeDataList<Order> FakeOrders = new List<Order>();
1
Stefan Dragnev On

You can just remove IgnoreInstance() from the last arrangement. This will make it work only when called on that Orders property.

Although, technically, you don't need to arrange any of those methods on the Orders property. ReturnsCollection will take care to proxy all IList<T> and all IQueryable<T> methods to the target (if the target implements any of those interfaces). Since FakeOrders is a List, then Add and Remove on Orders will be proxied to the same methods on the backing collection. AsQueryable is not an instance method, but an extension method. As such, you can't mock it for a specific derived type, but you shouldn't even need to. The original implementation of AsQueryable is flexible enough, so you shouldn't need to mock it at all, ever.

If you simply remove the last three arrangements, your test will still work as expected.