Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test that an expression is short-circuited

I have an extension method with the following signature:

public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
    ...
}

I have written a test-case for it that makes sure the two expressions are in fact combined. At least so that the new expression I get works.

Now I would like to write another test-case that just makes sure that the method uses the short-circuiting version of and. Any clue how I can do this?

I thought I could just do something like this:

    [Test]
    public void And_PredicatesAreShortCircuited()
    {
        var predicateNotUsed = true;
        Expression<Func<int, bool>> a = x => false;
        Expression<Func<int, bool>> b = x =>
            {
                predicateNotUsed = false;
                return true;
            };

        var foo = new[] { 1, 2, 3, 4, 5, 6, 7 }
            .Where(a.And(b).Compile())
            .ToArray();

        Assert.That(predicateNotUsed);
    }

But I get a giant red squiggly under that whole statement body for b stating that "A lambda expression with a statement body cannot be converted to an expression tree". So... any options? Or is this an impossible test to write?

like image 592
Svish Avatar asked Feb 27 '23 23:02

Svish


1 Answers

Simple suggestion: use a reference type instead of a value type, and dereference it in the path you don't want followed. Pass in null, and see whether it throws an exception :)

[Test]
public void And_PredicatesAreShortCircuited()
{
    Expression<Func<string, bool>> a = x => false;
    Expression<Func<string, bool>> b = x => x.Length > 10;

    var foo = new[] { null, null }
        .Where(a.And(b).Compile())
        .ToArray();
}

Another alternative would be to use some side-effecting function on the input data (e.g. pass in something that can be changed by the expression tree) but I think the above will probably be the simplest approach :)

Or another idea:

public T NonVoidFail(T x)
{
    Assert.Fail("I shouldn't be called");
    return x; // Will never happen
}

Then:

[Test]
public void And_PredicatesAreShortCircuited()
{
    Expression<Func<int, bool>> a = x => false;
    Expression<Func<int, bool>> b = x => NonVoidFail(x);

    var foo = new[] { 1, 2, 3, 4, 5, 6, 7 }
        .Where(a.And(b).Compile())
        .ToArray();
}

It's the same principle, but it'll give you a nicer exception :)

like image 104
Jon Skeet Avatar answered Mar 12 '23 11:03

Jon Skeet