Search code examples
c#.netmoqlinq-expressions

Why isn't my supplied predicate being matched when using Moq?


Given a simple type I'm testing, say

interface IMyRepo
{
  MyObj GetBy(Expression<Func<MyObj, bool> predicate);
}

so the standard mock setup works just fine:

_repoMock.Setup(x => x.GetBy(y => y.Prop=="A")).Returns(myObjInstance);

It matches on, for example: _repo.GetBy(x => x.Prop=="A")

However, if I have a helper method for my setup: _repoMock.Setup(Helpers.GenerateGetByMock<IMyRepo, MyObj>(y => y.Prop=="A"));

where

Expression<Func<T, R>> GenerateGetByMock<T, R>(Expression<Func<R,bool>> pred) where T : IMyRepo where R : class
{
  return x => x.GetBy(pred);
}

This will not match in a test on _repo.GetBy(x => x.Prop=="A")!

What is the difference in the generated expressions, and why do I have to use the explicit inline version? Is there any way of using the helper method approach?

Edit: Here's the mock expression with the inline version: enter image description here

versus with the helper method:

enter image description here


Solution

  • The issue is with how the compiler generates the Expression<Func<T, R>> from your helper method:

    Expression<Func<T, R>> GenerateGetByMock<T, R>(Expression<Func<R,bool>> pred) where T : IMyRepo where R : class
    {
      return x => x.GetBy(pred);
    }
    

    which doesn't compile, so presumably you actually mean this:

    Expression<Func<T, MyObj>> GenerateGetByMock<T>(Expression<Func<MyObj, bool>> pred)
    where T : IMyRepo {
        return x => x.GetBy(pred);
    }
    

    given that you have those types in your example:

    public interface IMyRepo {
        MyObj GetBy(Expression<Func<MyObj, bool>> predicate);
    }
    
    public class MyObj {
        public string Prop { get; set; }
    }
    

    the compiler for some reason isn't smart enough to do this:

    public Expression<Func<I, MyObj>> GenerateGetByMockManually<I>(
    Expression<Func<MyObj, bool>> predicate)
    where I : IMyRepo {
        // x
        var outerParameter =
           Expression.Parameter(typeof(I), "x");
        // x.GetBy
        var getByMethod = typeof(I).GetMethod("GetBy");
        //x.GetBy(predicate)
        var expressionCall = Expression.Call
            (outerParameter, getByMethod,
            [Expression.Quote(predicate)]); 
            // here the compiler just defaults to capturing the argument in a temp class field 
           // and expose it via constant
    
        //x => x.GetBy(predicate)
        var manualBuilt = Expression.Lambda<Func<I, MyObj>>
            (expressionCall, [outerParameter]);
    
        return manualBuilt;
        
        // Debugging
        var expressionVisitor = new GetByPredicateVisitor(predicate);
        var newExpression = expressionVisitor.Visit(manualBuilt);
    
        return (Expression<Func<I, MyObj>>)newExpression;
    }
    

    If you use the above method, things will work out as expected.

    If you still want to rely on the compiler for most of the work, you'd need to make use of an ExpressionVisitor implementation to modify the argument constant part and probably the most flexible way would be to create an extension method for this predicate quoting:

    public static class ExpressionQuoterExtensions {
    
        public static Expression<Func<I, M>> QuotePredicate<I, M>(
        this Expression<Func<I, M>> expression, Expression<Func<M, bool>> predicate) {
            var expressionVisitor = new PredicateVisitor<M>(predicate);
            return (Expression<Func<I, M>>)expressionVisitor.Visit(expression);
        }
    
        public class PredicateVisitor<M> : ExpressionVisitor {
            Expression<Func<M, bool>> _predicate;
    
            public PredicateVisitor(Expression<Func<M, bool>> predicate) {
                _predicate = predicate;
            }
    
            protected override Expression VisitMethodCall(MethodCallExpression node) {
                var newArguments = node.Arguments
                    .Select(argumentNode => {
                        // if predicate
                        if (argumentNode.Type.IsAssignableTo(typeof(Expression))
                           && argumentNode.Type.IsGenericType
                           && argumentNode.Type.GetGenericArguments().Count() == 1
                           && argumentNode.Type.GetGenericArguments()[0] == typeof(Func<M, bool>)
                           ) {
                            argumentNode.ToString().Dump(); //debugging
                            // can be __DisplayClass5_0`2[IMyRepo,MyObj]).predicate...
                            return Expression.Quote(_predicate);
                        }
                        return argumentNode;
                    })
                    .ToList();
    
                return Expression.Call(node.Object, node.Method, newArguments);
            }
        }
    }
    

    Then you'd need to modify your method a little:

    public Expression<Func<I, M>> GenerateGetByMockWithQuoter<I, M>(
        Expression<Func<M, bool>> predicate)
     where I : IMyRepo
     where M : class {
        Expression<Func<I, M>> expression = x => x.GetBy<M>(predicate);
        return expression.QuotePredicate(predicate);
    }
    

    OP's fiddle - modified to use the extension method - working.

    Some manual testing code:

    var _repoMock = new Mock<IMyRepo>();
    
    var manualExpression = GenerateGetByMockManually<IMyRepo>(y => y.Prop == "A");
    _repoMock
    //.Setup(x => x.GetBy(y => y.Prop == "A"))
    .Setup(manualExpression)
    .Returns(new MyObj() {
        Prop = "foo"
    });
    Console.WriteLine(_repoMock.Object.GetBy(x => x.Prop == "A").Prop); // foo
    
    var compilerAssistedExpression = GenerateGetByMockWithQuoter<IMyRepo,MyObj>(y => y.Prop == "B");
    _repoMock
    .Setup(compilerAssistedExpression)
    .Returns(new MyObj() {
        Prop = "bar"
    });
    Console.WriteLine(_repoMock.Object.GetBy(x => x.Prop == "B").Prop); // bar