Sunday, 18 January 2009

Fluent Test Data Builders Using Generic Extension Methods and Lambda Expressions

Whilst writing some extension methods this evening to make creating objects in unit tests simpler I found myself writing methods that looked sufficiently similar to each other that I felt the code lacked a little DRYness.

The code being written has to output a data extract in a fixed field length formatted text file. As such the length of the formatted fields is very important, so I had created some methods to initialise the properties of my object with given lengths so that I could ensure the desired behaviour during formatting (such as trimming, padding, exception throwing, etc...). These methods looked much like this:

public static DataExtract WithSetLengthSurname(this DataExtract dataExtract, int length) 
{ 
    dataExtract.Surname = string.Empty.PadRight(length); 
    return dataExtract; 
}
 
public static DataExtract WithSetLengthForenames(this DataExtract dataExtract, int length) 
{ 
    dataExtract.Forenames = string.Empty.PadRight(length); 
    return dataExtract; 
} 
 

As you can see these methods are very similar. What I wanted I felt was to take this fluent interface for creating my DataExtract object, but to make the whole thing more generic.


This is what I came up with:


public static T SetLength<T>(this T obj, Expression<Func<T,string>> expression, int length) 
{ 
    string memberName = getMemberExpressionMemberName(expression.Body); 
    string currentValue = expression.Compile().Invoke(obj) ?? string.Empty; 
    string newValue = currentValue.PadRight(length); 
    obj.GetType().GetProperty(memberName).SetValue(obj, newValue, null); 
    return obj; 
} 
 
private static string getMemberExpressionMemberName (Expression expression) 
{ 
    if (expression == null) return string.Empty; 
 
    switch (expression.NodeType) 
    { 
        case ExpressionType.MemberAccess: 
            { 
                MemberExpression memberExpression = (MemberExpression) expression; 
                return memberExpression.Member.Name; 
            } 
        default: 
            throw new InvalidOperationException(string.Format("The Expression Type was not expected: {0}", expression.NodeType)); 
    } 
 
}

With this code then I can now make a call like this:


DataExtract extract = new DataExtract().SetLength((e) => e.Forenames, 10);

Now it's not unreasonable to look at this and wonder what has been gained, after all I could have written this:


DataExtract extract = new DataExtract { Forenames = "".PadRight(10) };

I suppose for me it comes into its own when combined with other extension methods as a part of a wider ranging Fluent Interface based test object builder, like this:


CandidateExtract extract = new CandidateExtract() 
    .SetLength((e) => e.Surname, 36) 
    .WithValidQualificationCode() 
    .WithValidQualificationTitle() 
    .SetLength((e) => e.Forenames, 36);

As can be seen in the above some property specific methods still exist, because the behaviour that each implements is sufficiently unique, but where the behaviour is very similar (such as setting a string to a default length) this seems to me to provide a number of benefits:


  • the number of methods appearing in Intellisense drops making it easier to find what you want quickly
  • the number of builder methods to be written drops (in my case significantly)
  • by using a fluent interface readability is enhanced.

1 comment:

Gareth said...

Please take a look at NBuilder:

http://shouldbeableto.wordpress.com/2009/02/01/3/