Search code examples
linqunit-testingtddentity-framework-4.1moq

Moq testing LINQ Where queries


I'm using EF 4.1 to build a domain model. I have a Task class with a Validate(string userCode) method and in it I want to ensure the user code maps to a valid user in the database, so:

public static bool Validate(string userCode)
{
    IDbSet<User> users = db.Set<User>();
    var results = from u in users
              where u.UserCode.Equals(userCode)
              select u;
    return results.FirstOrDefault() != null;
}

I can use Moq to mock IDbSet no problem. But ran into trouble with the Where call:

User user = new User { UserCode = "abc" };
IList<User> list = new List<User> { user };
var users = new Mock<IDbSet<User>>();
users.Setup(x => x.Where(It.IsAny<Expression<Func<User, bool>>>())).Returns(list.AsQueryable);

Initialization method JLTi.iRIS3.Tests.TaskTest.SetUp threw exception.
System.NotSupportedException: System.NotSupportedException: Expression 
references a method that does not belong to the mocked object:
x => x.Where<User>(It.IsAny<Expression`1>()).

Other than creating a level of indirection (eg, using a ServiceLocator to get an object that runs the LINQ and then mock that method) I can't think of how else to test this, but I want to make sure there is no way before I introduce another layer. And I can see this kind of LINQ queries will be needed quite often so the service objects can quickly spiral out of control.

Could some kind soul help? Thanks!


Solution

  • As I know Moq is able to set up only virtual methods of mocked object itself but you are trying to set up extensions (static) method - no way! These methods are absolutely outside of your mock scope.

    Moreover that code is hard to test and requires too much initialization to be able to test it. Use this instead:

    internal virtual IQueryable<User> GetUserSet()
    {
        return db.Set<User>();
    } 
    
    public bool Validate(string userCode)
    {
        IQueryable<User> users = GetUserSet();
        var results = from u in users
                      where u.UserCode.Equals(userCode)
                      select u;
        return results.FirstOrDefault() != null;
    }
    

    You will just need to set up GetUserSet to return your list. Such testing has some major issues:

    • You are not testing the real implementation - in case of EF mocking sets is stupid approach because once you do it you change linq-to-entities to linq-to-objects. Those two are totally different and linq-to-entities is only small subset of linq-to-objects = your unit tests can pass with linq-to-objects but your code will fail at runtime.
    • Once you use this approach you cannot use Include because include is dependent on DbQuery / DbSet. Again you need integration test to use it.
    • This doesn't test that your lazy loading works

    The better approach is removing your linq queries from Validate method - just call them as another virtual method of the object. Unit test your Validate method with mocked query methods and use integration tests to test queries themselves.