3

Can I use an Expression Tree as an argument constraint in a FakeIteasy CallTo assertion?

Given a method on an interface with the following signature:

interface IRepository<TEntity>
{
    TEntity Single(Expression<Func<TEntity, bool>> predicate);

Being called in code like so:

Flight flight = repository.Single(f => f.ID == id);

I have in mind a unit test doing something like this:

Expression<Func<Flight, bool>> myExpression = flight => flight.ID == 1;

A.CallTo(() => repository.Single(
                  A<Expression<Func<Flight, bool>>>.That.Matches(myExpression)))
                  .Returns(new Flight());

However this produces a warning: Try specifying type arguments explicitly.

I am currently having to use the Ignored property which is not ideal.

riQQ
  • 9,878
  • 7
  • 49
  • 66

3 Answers3

3

The "Matches"-method takes a lambda but you're trying to pass it the expression. What are you trying to say with the "Matches"-call? Are you matching on equality? In that case you'd just write:

A.CallTo(() => repository.Single(myExpression)).Returns(new Flight());

If you want to constrain the expression on something else you'd have to pass a predicate of the type: Func<Expression<Func<Flight, bool>>, bool> to the "Matches"-method.

Patrik Hägne
  • 16,751
  • 5
  • 52
  • 60
  • I tried this first but the call is never intercepted, making the fake strict confirms this. Tried Matches(a => a == myExpression), also not intercepted. – Michael.McD Oct 07 '11 at 23:27
  • What you want is to match on equality? How is the expression passed down to the repository from the test? It seems a bit odd to match expressions on equality in most cases (not all), what you want in most cases would be a test that determins whether the passed in expression is equivalent, not equal. You say that the call is not intercepted at all, in other words if you make the fake strict it doesn't break? – Patrik Hägne Oct 09 '11 at 19:23
  • Hi @Patric, Making the fake strict throws an ExpectationException(a call to unconfigured fake). The call on the repository I want to intercept looks like this repository.Single(f => f.ID == id) and I want to intercept it when the variable id is 1. – Michael.McD Oct 09 '11 at 21:28
  • Then you can't use equality comparison, you'd have to pass a matches-predicate that examines the expression. The Equals-method of the Expression-type does not do this, it tests for instance-equality. – Patrik Hägne Oct 10 '11 at 06:38
2

Thanks Patrik,

Examining the expression was exactly what I needed to do, i.e. parse the expression (f => f.ID == id) and execute the Right side of the == to get its runtime value.

In code this looks like this:

A.CallTo(() => flightRepository.Single(A<Expression<Func<Flight, bool>>>.That
                .Matches(exp => Expression.Lambda<Func<int>>(((BinaryExpression)exp.Body).Right).Compile().Invoke() == 1)))
                .Returns(new Flight());

However I can't help thinking that there must be a more elegant way to achieve the same end. I'll leave that for another day though.

Thanks again, Michael McDowell

  • I think the more elegant solution is to depend on another abstraction than the IRepository, this might be IFlightRepository for example (but there are other compelling alternatives as well). If IFlightRepository exposes a method "GetFlightById" you would have no problems testing this. The implementation of a flight repository on the other hand can very well depend on IRepository this is very close to the metal and needs no unit testing but should be covered by integration tests. This is very much my personal opinion though. – Patrik Hägne Oct 14 '11 at 20:16
  • Yep, definitlay needs another layer to abstract my EntLib data access code away. I'm currently struggling with lots of diffrent solutions though. Any pointers would be much appreciated. – Michael.McD Oct 14 '11 at 23:43
1

I had the same problem while attempting to assert an expression as an argument but I was using Moq. The solution should work for you though as well...

I give most of the credit to this answer to a similar question: Moq Expect On IRepository Passing Expression

It basically says you can do a ToString() on the expressions and compare them. It is kind of hacky but it only has one downside; the variables names in the lambda expression must match.

Here is an example...

    [Test]
    public void TestWhichComparesExpressions()
    {
        // setup
        _mockRepository.Setup(x => x.GetByFilter(MatchQuery())).Returns(new List<Record>());

        // execute
        var records = _service.GetRecordsByFilter();

        // assert
        Assert.IsNotNull(records);
        Assert.AreEqual(0, records.Count());
    }

    private static Expression<Func<DomainRecord, bool>> MatchQuery()
    {
        return MatchExpression(ServiceClass.QueryForTheRecords); // constant
    }

    // https://stackoverflow.com/questions/288413/moq-expect-on-irepository-passing-expression/1120836#1120836
    private static Expression<Func<DomainRecord, bool>> MatchExpression(Expression<Func<DomainRecord, bool>> expression)
    {
        return It.Is<Expression<Func<DomainRecord, bool>>>(e => e.ToString() == expression.ToString());
    }

I decided to put the expression into a constant on the class which used it which guaranteed it would be the same in the test if someone changed the lambda expressions's variable names.

Community
  • 1
  • 1
Jesse Webb
  • 43,135
  • 27
  • 106
  • 143