Mocking data sets with ExpressionTrees

In my last post I gave a simple explanation of ExpressionTree’s – and in this post I’m going to explore the power we can expose with them.
The Background
Recently we had a problem at work – we wanted to write a testing framework that would run a very large amount of data through a class. The component was critical, and we wanted to see how the results would trend as different parameters varied.
Our input object had 130 different properties, broken into various different classes. It was going to be tedious to produce all the different test cases, and it was going to lead to some very ugly code.
The solution
We ended up writing some extension methods that used an ExpressionTree to parse through the objects, and create the variants. The core of our solution was around this method:
1 2 3 4 5 6 7 8 | public static IEnumerable<MemberExpression> GetMembersOnPath(this MemberExpression expression) { while (expression != null) { yield return expression; expression = expression.Expression as MemberExpression; } } |
This useful method allows us to parse through a MemberExpression (a type of ExpressionTree, where the compiled expression returns the value of a property or field) and return all the members on the path. For example, if we had an expression:
x => x.SomeObject.AnotherObject.SomeProperty; |
Then the members on the path would be:
members = { SomeProperty, AnotherObject, SomeObject } |
With this trick we were able to write this code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | public static class DataMockingExtensions { public static IEnumerable<TX> MockWith<TX, TY>(this TX obj, Expression<Func<TX, TY>> selector, IEnumerable<TY> values) { if (selector.Body.NodeType != ExpressionType.MemberAccess) { throw new InvalidOperationException(); } var path = ((MemberExpression)selector.Body).GetMembersOnPath().Reverse().ToList(); foreach (var value in values) { var item = obj.DeepClone(); var handle = (object)item; // traverse object to the parent of the property for (var i = 0; i < path.Count - 1; i++) { var property = (PropertyInfo)path[i].Member; handle = property.GetValue(handle, null); } // set the value of the property, and return handle.GetType().GetProperty(path.Last().Member.Name).SetValue(handle, value); yield return item; } } } |
Two important things to note. Firstly, we specifically use the body of the expression tree a number of times here. Inside an ExpressionTree we have a number of components, and the body refers to the body of the expression – i.e the code block, which in this case is a simple block that refers to a property on an object. Secondly, we also check the NodeType of the body. The NodeType represents the type of operation, and a full list of ExpressionType’s can be found here.
The remaining code above simply creates a copy of the original object (full code below), and then uses the members to traverse and set each individual permutation of the data, with some simple reflection. We can use this second method to chain the calls together, so we can set multiple different properties:
1 2 3 4 5 6 7 8 9 10 | public static IEnumerable<TX> AndMockWith<TX, TY>(this IEnumerable<TX> list, Expression<Func<TX, TY>> selector, IEnumerable<TY> values) { foreach (var enumerator in list.Select(item => item.MockWith(selector, values).GetEnumerator())) { while (enumerator.MoveNext()) { yield return enumerator.Current; } } } |
With this code we can now create a large number of permutations very easily, with this code:
1 2 3 4 5 6 7 | var obj = new TestClass(); var list = obj.MockWith(x => x.TestInt, new[] { 1, 2, 3 }) .AndMockWith(x => x.TestDecimal, new[] { 2.1m, 3.1m }) .AndMockWith(x => x.TestString, new[] { "test1", "test2", "" }) .AndMockWith(x => x.ChildObject.TestChildPropertyInt, new[] { 1, 2 }) .ToList(); // this produces a list with 36 unique permutations |
Design Principles
There are a number of ways I could have solved this problem, but I like this solution for two main reasons:
1. The code in my test harness is succinct and clear – there’s no extra code to set this up, and the code communicates my intent.
2. I don’t need to rely on magic strings, or anything special to traverse my properties (as I might need to if I used pure reflection). This is good for a number of reasons, but key is that the code will break at compile time, not run time (making it easier to catch, should I refactor my objects).
For reference, this is the supporting code for the above:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | public static string Serialize(this object obj) { XmlSerializer serializer = new XmlSerializer(obj.GetType()); using (StringWriter writer = new StringWriter()) { serializer.Serialize(writer, obj); return writer.ToString(); } } public static T Deserialize<T>(this string item) { if (string.IsNullOrWhiteSpace(item)) return default(T); var serializer = new XmlSerializer(typeof(T)); using (var reader = new StringReader(item)) { try { return (T)serializer.Deserialize(reader); } catch (Exception) { return default(T); } } } public static T DeepClone<T>(this T object) { if(ReferenceEquals(object, null) { return default(T); } return object.Serialize().Deserialize<T>(); } |
Photo Credit: Rich Anderson
Leave a Reply