justmockarrange-act-assert

JustMock - How to mock a method for all instances by returning the result of the equivalent method of another class (sharing a common interface


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


Solution

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