Anonymous Delegates, Parameters and Unit Testing

Anonymous delegates, lambdas and their akin are here to stay and make our life easier.

Despite being in the framework since day one (well, not in their anonymous incarnation and definitely not in their lambda form) delegates have had some sort of “Cambrian explosion” in their usage since the advent of LINQ. More and more developers have been exposed to their benefits and more a more APIs make use of them.

And all that is fine by me. But the other day one of the developers that works with me had some trouble testing a piece of code he wrote and called me in. Do not why, but they always call me when there is trouble…

The code was something like this simplification:

Subject collaborator

The Subject class needs to be tested, well, its BarMethod() and FooMethod(), that, in turn, will call a method on a collaborator that returns a collection of Something objects that will satisfy a given predicate sent to that method as an argument. In code:

public class Subject : ISubject
{
    private readonly ICollaborator _collaborator;
    public Subject(ICollaborator collaborator)
    {
        _collaborator = collaborator;
    }

    public IEnumerable<Something> FooMethod()
    {
        return _collaborator.Baz(s => s.Foo > 0);
    }
    public IEnumerable<Something> BarMethod()
    {
        return _collaborator.Baz(s => s.Bar > 0);
    }
}
public interface ICollaborator
{
    IEnumerable<Something> Baz(Func<Something, bool> predicate);
}

And what is what was giving that developer a headache? Testing that the collaborator is called with the correct delegate for each method of the SUT (System Under Test). As always, there are multiple ways to solve a problem. All of them come down to a point: we need to get the arguments of the call to the collaborator.

In our case we are using Rhino Mocks to isolate from the ICollaborator implementation. The technique we ended up implementing involved several steps:

  1. getting the arguments of the call to the Baz() method via GetArgumentsForCallsMadeOn() extension method. That, effectively, give us access to the actual delegate used in the collaboration
  2. applying that delegate to a controlled object
  3. asserting on the execution of the delegate over that controlled object

With that technique tests can be easily written as:

[Test]
public void FooMethod_CallsCollaboratorWithFooDelgate()
{
    ISubject subject = initSubject();

    subject.FooMethod();

    Func<Something, bool> predicate = getPredicateArgument();
    Something satisfiesPredicate = new Something { Foo = 1, Bar = -1 };
    Assert.That(predicate(satisfiesPredicate), Is.True, "can only be true if Foo > 0, hence Foo is used in the delegate");
}

[Test]
public void BarMethod_CallsCollaboratorWithBarDelgate()
{
    ISubject subject = initSubject();

    subject.BarMethod();

    Func<Something, bool> predicate = getPredicateArgument();
    Something satisfiesPredicate = new Something { Foo = -1, Bar = 1 };
    Assert.That(predicate(satisfiesPredicate), Is.True, "can only be true if Bar > 0, hence Bar is used in the delegate");
}

What I do not like

It works but is a bit convoluted. The flow is not “natural” and some value indirection (applying the actual delegate to a controlled argument) is required to “guess into” what’s inside the delegate.
My ease of mind with the solution comes from the rationalization of the fact that delegates themselves provide a level of indirection on top of the action to perform and tests suffer from that indirection.

What else could be done instead

Obtaining the argument form the collaboration is something that needs to be performed regardless; but applying the delegate could be spared if there was a way to peek inside the delegate.
And there is: an expression can be used instead of a delegate. Thus, in the test, the expression needs to be walked through looking for the expected property.

Why is not the proposed solution then? To start with it does not take away the problem of non-obvious flow; it merely changes the assertion mechanics. Another problem is that the collaborator implementation needs to change to accommodate the expression and compile it before applying it to the objects and there is a price to pay in runtime to compile the expression to its executable delegate form. Am I happy to pay that price (and we have not talked how big the price is) for the assertion not to be substantially better? Answer should be obvious by now, as you are reading this paragraph this late in the post.

As always, code can be found in my corner of Google code.

Daniel Gonzalez Garcia
Vertica A/S

Kategorier: Udvikling

Tagged as: ,

Skriv et svar

Udfyld dine oplysninger nedenfor eller klik på et ikon for at logge ind:

WordPress.com Logo

Du kommenterer med din WordPress.com konto. Log Out / Skift )

Twitter picture

Du kommenterer med din Twitter konto. Log Out / Skift )

Facebook photo

Du kommenterer med din Facebook konto. Log Out / Skift )

Google+ photo

Du kommenterer med din Google+ konto. Log Out / Skift )

Connecting to %s