Making string based method strongly typed

I can't believe they implemented this way – was the first thought on my mind…
The method in question is part of the new(er) NUnit’s constraint based model specifically the one used to assert a property value.
Before we dive deeper a few words on the constraint based model:

constraint based model

Most unit testing framework have similar way of writing assertions – if you’ve wrote a test in the past it probably looked something like this:
Assert.AreEqual(4, 2 + 2);

NUnit has another API for assertion which is more to my liking and using it for the above assertion would look like this:

Assert.That(2 + 2, Is.EqualTo(4));

Using this API I can write pretty cool things:

// Simple values
Assert.That(someInt, Is.Negative);
Assert.That(someString, Is.Not.Null.And.Not.Empty);
Assert.That(someObject, Is.Null);

// Collections
Assert.That(someList, Is.Not.Empty);
Assert.That(intArray, Is.All.GreaterThan(10); // All intergers in array are bigger than 10
Assert.That(array, Has.Exactly(1).GreaterThan(3)); // Exactly one item bigger than 3
Assert.That(someEnumerable, Is.All.Property("MyProp").EqualTo(25));

I have an issue with the last line. I don’t like writing string based code – mainly it tend to break on trivial refactoring and writing assertion that could break on runtime  just because someone has changed the property name seems just wrong.
I have as compiler and I’m not afraid to use it – the least you can do is give me compile time errors!

The solution

I’ve wanted to add another method to Is.All and so I’ve used one of fluent API writers friend – the extension method and I got the following API:

Assert.That(someEnumerable, Is.All.Property<MyType>(m => m.MyProp).EqualTo(25));

Using black lambda tricks I was able to create a strongly typed API that receive a property.

After I had my entry point sorted out all I needed to do is write (and copy shamelessly) some cool Expression based code to get the property name and it works!

Here is the full code:

public static ResolvableConstraintExpression Property<T>(
  this NUnit.Framework.Constraints.ConstraintExpression expression,   Expression<func<T, object>> lambda)
{
    MemberExpression memberExpression = null;
    switch (lambda.Body.NodeType)
    {
        case ExpressionType.Convert:
            // lambda is obj => Convert(obj.Prop) - the operand of conversion is our member expression
            var unary = lambda.Body as UnaryExpression;
            if (unary == null)
            {
                Assert.Fail("Cannot parse expression");
            }
            memberExpression = unary.Operand as MemberExpression;
            break;

        case ExpressionType.MemberAccess:
            // lambda is (obj => obj.Prop) - the body is the member expression
            memberExpression = lambda.Body as MemberExpression;
            break;

        default:
            Assert.Fail("Cannot parse expression");
            break;
    }

    if (memberExpression == null ||  string.IsNullOrEmpty(memberExpression.Member.Name))
    {
        Assert.Fail("Labda body is not MemberExpression - use only properties");
    }

    var propertyName = memberExpression.Member.Name;

    return expression.Property(propertyName);
}


I hope it would help at least one reader who cannot accept a string based API.

Happy Coding…

Labels: , , , ,