18

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.

ToddB
  • 1,464
  • 1
  • 12
  • 27

3 Answers3

14

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.

Nick is tired
  • 6,860
  • 20
  • 39
  • 51
Charlie
  • 624
  • 1
  • 8
  • 22
  • Thanks @carlos-alejo. This is actually a bit different then what I am trying to pull off. I have updated the question a little to help clarify. I am not trying to test the lambda. I am trying to get the Moq proxy to return the defined object in the "setup" call. So inside the controller the binding manager is called with this lambda as an expression as the parameter (I have verified the username's match). – ToddB May 25 '16 at 14:44
  • @ToddB I am so sorry, but still I'm not sure if I understand your question. I know you are not trying to test the lambda expression. But if you want to return what you set up in the mock, you must make sure that the lambda you pass as parameter complies with the `It.Is<>` clause. In that clause you are comparing two lambdas with the `==` operator, but that does not work. So you will have to compare the lambdas by another mean, and that what I was trying to say in the answer. If this does not help, please help me understand better your problem. – Charlie May 25 '16 at 14:57
  • Thanks @carlos-alejo. I feel I am not doing a good job explaining either. I think the compile step is what I am not understanding. I have felt like I need to call Compile() someplace but I have not been able to get any of the syntax setup correctly. Are you saying do something more like this (syntax of course is not correct)? `this.bindingManager.Setup( c => c.GetUserBinding(It.Is>>(testExpression.Compile())))` – ToddB May 25 '16 at 16:03
  • I have been looking at [this blog](http://awkwardcoder.com/2013/04/24/constraining-mocks-with-expression-arguments/) where he calls. `MockedRepository .Setup(x => x.Query( Moq.It.Is>>( y => y.Compile()(activePreferenceForPhysician)))) .Returns(_expectedPhysicianPreferences);` but I don't follow how this is setting up the expected input lambda parameter. – ToddB May 25 '16 at 16:24
  • Figured out how to use Compile for this. Updating my question to reflect how to make this work. – ToddB May 25 '16 at 18:25
  • @ToddB Sorry I couldn't access SO in a while, but I'm glad you came up with the solution! Thank you for accepting my answer! – Charlie May 25 '16 at 21:19
9

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.

Artem A
  • 2,154
  • 2
  • 23
  • 30
  • 2
    This is so simple and useful. With this one you can make a nice test reusing the expression that is written in you class. – EduLopez May 26 '20 at 18:44
  • When I try this I get System.ArgumentException : Invalid callback. Setup on method with parameters (Expression>) cannot invoke callback with parameters (Expression>[]). What did I miss? – falowil Nov 25 '22 at 16:17
  • Maybe some changes in moq framework. Need to debug, because this is a part of my working code. – Artem A Nov 30 '22 at 10:57
0

In some cases you might be able to evaluate the expression into something comparable, like a string.

It.Is<Expression<Func<UserBinding, bool>>>(expression => SomeEvaluator.Evaluate (expression) == "Username = 'Testing Framework'");

In my case I wanted to mock queries to Azure Tables, so I used Azure.Data.Tables.TableClient.CreateQueryFilter for evaluation, i.e.:

It.Is<Expression<Func<TableEntity, bool>>> (query => TableClient.CreateQueryFilter (query) == "RowKey eq 'someId'")
Karlas
  • 981
  • 6
  • 5