Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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!

like image 599
Mark Arrowsmith Avatar asked Jul 11 '11 04:07

Mark Arrowsmith


4 Answers

There is an article on MSDN highlighting how to mock using moq: The gist of it is to represent linq to entities operations with linq to objects.

var mockSet = new Mock<DbSet<Blog>>(); 
mockSet.As<IQueryable<Blog>>().Setup(m => m.Provider).Returns(data.Provider); 
mockSet.As<IQueryable<Blog>>().Setup(m => m.Expression).Returns(data.Expression); 
mockSet.As<IQueryable<Blog>>().Setup(m => m.ElementType).Returns(data.ElementType); 
mockSet.As<IQueryable<Blog>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());

As Ladislav points out there are disadvantages to this as Linq To Objects is simply different to Linq to Entities so it may result in false positives. But it now being an MSDN article it does point that it is at least possible and perhaps recommended in some cases?

One thing that may of changed since the original answers to this post is that the Entity Framework team have opened up areas of Entity Framework in EF 6.0 to make it easier to mock it's inners.

like image 57
Alex KeySmith Avatar answered Nov 18 '22 06:11

Alex KeySmith


Although I have not tried this, because IDBSet implements IEnumerable you might have to mock the enumerator method so the linq statements will pick up your list of users. You don't actually want to mock linq but by the looks of your code you want to test whether you are finding the right user based on the UserCode which I think is a valid unit test.

 var user = new User { UserCode = "abc" };
 var list = new List<User> { user };
 var users = new Mock<IDbSet<User>>();
 users.Setup(x => x.GetEnumerator()).Returns(list.GetEnumerator());

You might get a conflict with the non-generic version of the GetEnumerator but it this might help you on the right track. Then you have to then place the mocked object on the data context which depends on other code that we don't see.

like image 35
aqwert Avatar answered Nov 18 '22 07:11

aqwert


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.

like image 9
Ladislav Mrnka Avatar answered Nov 18 '22 07:11

Ladislav Mrnka


I found it easier just to write the stub:

internal class FakeDbSet<T> : IDbSet<T>where T : class
{
    readonly HashSet<T> _data;
    readonly IQueryable _query;

    public FakeDbSet()
    {
        _data = new HashSet<T>();
        _query = _data.AsQueryable();
    }

    public virtual T Find(params object[] keyValues)
    {
        throw new NotImplementedException("Derive from FakeDbSet<T> and override Find");
    }

    public T Add(T item)
    {
        _data.Add(item);
        return item;
    }

    public T Remove(T item)
    {
        _data.Remove(item);
        return item;
    }

    public T Attach(T item)
    {
        _data.Add(item);
        return item;
    }

    public void Detach(T item)
    {
        _data.Remove(item);
    }

    Type IQueryable.ElementType
    {
        get { return _query.ElementType; }
    }

    Expression IQueryable.Expression
    {
        get { return _query.Expression; }
    }

    IQueryProvider IQueryable.Provider
    {
        get { return _query.Provider; }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _data.GetEnumerator();
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        return _data.GetEnumerator();
    }


    public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T
    {
        return Activator.CreateInstance<TDerivedEntity>();
    }

    public T Create()
    {
        return Activator.CreateInstance<T>();
    }

    public ObservableCollection<T> Local
    {
        get
        {

            return new ObservableCollection<T>(_data);
        }
    }
like image 3
Lee Smith Avatar answered Nov 18 '22 07:11

Lee Smith