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:
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 :)
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>();