'Moq Expression with Constraint ... It.Is<Expression<Func<T, bool>>>

Ok, I am having a hard time trying to figure out how to setup a moq for a method that takes in an expression. There are a lot of examples out there of how to to It.IsAny<>... that is not what I am after. I am after doing with a constraint, so It.Is<>. I have set it up but it never returns the value I have asked it to return.

// Expression being setup
Expression<Func<UserBinding, bool>> testExpression = binding =>
binding.User.Username == "Testing Framework";


// Setup of what expression to look for. 
 this.bindingManager.Setup(
            c => c.GetUserBinding(It.Is<Expression<Func<UserBinding, bool>>>
(criteria => criteria == testExpression)))
            .Returns(new List<UserBinding>() { testBinding }.AsQueryable());

// Line of code call from within the class being tested. So this is being mocked and should return the defined object when the same lambda is passed in.
 this.bindingManager.GetUserBinding(b => b.User.Username == username)
                .SingleOrDefault();

// class under test. So for the test this is being called. 
// so this is the method being called and inside this method is where the binding manager is being mocked and called. 
var response = this.controller.SendMessage(message, true).Result;

        response.StatusCode.Should().Be(HttpStatusCode.BadRequest);

 // inside the controller.SendMessage method this method is called with the lambda expression. I have verified the usernames match but when the setup is It.Is this returns null instead of the object setup in the "setup" call. 
this.bindingManager.GetUserBinding(b => b.User.Username == username)
                .SingleOrDefault();

If I change the setup to It.IsAny... It works and returns the expected object setup in the "returns" method.

I have found a few examples of how to do this on the web one is doing it this way the other is using compile but I can't get that to work either. How do you get this to work for a specific expression?

Update with working solution based on answer


@carlos-alejo got me going in the right direction or at least kicked me back to the Compile action. I was thinking about it wrong. I have the solution working now based on using compile. The key thing to understand about compile is you are giving it an object by which to evaluate/generate the expression for.

So in my case if some one is giving me an expression like this:

binding => binding.User.Username == "Testing Framework";

I need to have a UserBinding like this:

var testBinding = new UserBinding { Binding = new Binding { Name = "Default binding" }, Domain = "test.com", User = new User() { Username = "Testing Framework" } };

I can then create my "setup" call like this:

this.bindingManager.Setup(c => c.GetUserBinding(It.Is<Expression<Func<UserBinding, bool>>>(y => y.Compile()(testBinding))))
        .Returns(new List<UserBinding>() { testBinding }.AsQueryable());

This works and in my case returns me back the test binding object as I have setup. If you change the testBinding to be (notice I changed the user name):

    var testBinding = new UserBinding { Binding = new Binding { Name = "Default binding" }, Domain = "test.com", User = new User() { Username = "Testing Framework2" } };

It will not work because the code inside my system under test generates an expression looking for "Test Framework"

Maybe it was just me not connecting the dots on this but hopefully it helps others.



Solution 1:[1]

It seems that the real problem here is how to compare two lambda expressions, as you try to do in the It.Is<Expression<Func<UserBinding, bool>>> (criteria => criteria == testExpression) clause. Using @neleus's answer to this question, I could come up with this test that actually passes:

readonly Mock<IBindingManager> bindingManager = new Mock<IBindingManager>();
    
[Test]
public void TestMethod()
{
    Expression<Func<string, bool>> testExpression = binding => (binding == "Testing Framework");
        
    bindingManager.Setup(c => c.GetUserBinding(It.Is<Expression<Func<string, bool>>>(
        criteria => LambdaCompare.Eq(criteria, testExpression)))).Returns(new List<string>());
        
    var oc = new OtherClass(bindingManager.Object);
        
    var actual = oc.Test(b => b == "Testing Framework");
        
    Assert.That(actual, Is.Not.Null);
    bindingManager.Verify(c => c.GetUserBinding(It.Is<Expression<Func<string, bool>>>(
        criteria => LambdaCompare.Eq(criteria, testExpression))), Times.Once());
}

Please note the use of the LambdaCompare.Eq static method to compare that the expressions are the same. If I compare the expressions just with == or even Equals, the test fails.

Solution 2:[2]

When I was looking for the way to to mock Where() and filter some data, in code under tests looks like:

Repository<Customer>().Where(x=>x.IsActive).ToList() 

I could design such example based on answers form others:

 var inputTestDataAsNonFilteredCustomers = new List<Customer> {cust1, cust2};
 var customersRepoMock = new Mock<IBaseRepository<Customer>>();

                IQueryable<Customer> filteredResult = null;
                customersRepoMock.Setup(x => x.Where(It.IsAny<Expression<Func<Customer, bool>>>()))
                    .Callback((Expression<Func<Customer, bool>>[] expressions) =>
                    {
                        if (expressions == null || expressions.Any() == false)
                        {
                            return;
                        }
                        Func<Customer, bool> wereLambdaExpression = expressions.First().Compile();  //  x=>x.isActive is here
                        filteredResult = inputTestDataAsNonFilteredCustomers.Where(wereLambdaExpression).ToList().AsQueryable();// x=>x.isActive was applied
                    })
                   .Returns(() => filteredResult.AsQueryable());

Maybe it will be helpful for feather developers.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Nick stands with Ukraine
Solution 2 Artem G