Easier object matching in TDD with SpecsFor

I love using SpecsFor to write tests. However, there’s a function in the library called ShouldLookLikePartial that works on anonymous objects, that has always left a little to be desired. Recently I decided to fix it, and with the next release of SpecsFor, my change will be in! This involved me diving into some expression parsing, which was much easier than I thought it would be. I also thought it was pretty interesting, so I decided to share what I dug up. Read on if you’re interested!

The Problem

One common thing you do when testing is assert that your result (say a view model) looks like you expect. But lets say your result involves a new domain object being created and saved. You want to verify its properties are set correctly, but there’s probably some fields you don’t care about (like an ID).

Originally, you would use the ShouldLookLikePartial method in SpecsFor.

[Test]
public void then_only_certain_properties_are_set()
{
    SUT.ShouldLookLikePartial(new { Name = "blah" });
}

This accepts an anonymous object, so it only compares what you’ve defined. However, you’ve lost one of the big advantages of a statically typed language. Your tests are no longer refactor friendly. We want our feedback cycle to tell us ASAP when something is wrong. If you renamed a field, your code would still build. It wouldn’t fail until you ran your tests. With tools like ReSharper, renaming a field is typically very safe, but we’ve just made it a little more brittle.

The Solution

I worked with Matt Honeycutt (owner and creator of SpecsFor) to come up with the syntax we wanted - which is pretty much “works just like the old Partial version, except you declare your type now”. So that’s what I did!

[Test]
public void then_only_certain_properties_are_set()
{
    SUT.ShouldLookLike(() => new TestObject { Name = "blah" });
}

 

It works with more than just a simple object though. We can even test nested complex objects:

SUT.ShouldLookLike(() => new TestObject
             {
                 Name = "Test",
                 Awesomeness = 11,
                 Nested = new TestObject
                 {
                     Name = "nested 1 test",
                     Nested = new TestObject
                     {
                         Name = "ULTRA NEST COMBO KILL",
                        Awesomeness = 69 //thanks, Bill & Ted, real mature.
                     }
                 }
             })

As well as IEnumerables:

list.ShouldLookLike(() => new[]
                {
                    new TestObject
                        {
                            Name = "one",
                            Awesomeness = 1
                        },
                    new TestObject
                        {
                            Name = "two",
                            Awesomeness = 2
                        }
                })

And you can of course mix and match all that:

SUT.ShouldLookLike(() => new TestObject
            {
                Name = "Test",
                Awesomeness = 11,
                Nested = new TestObject
                {
                    Name = "nested 1 test",
                    NestedArray = new []
                        {
                            new TestObject
                                {
                                    Name = "level 1 nested 1",
                                    Awesomeness = 11
                                },
                            new TestObject
                                {
                                    Name = "level 1 nested 2",
                                    NestedArray = new []
                                        {
                                            new TestObject
                                                {
                                                    Name = "lets get nested son"
                                                }
                                        }
                                }
                        },
                    Nested = new TestObject
                    {
                        Name = "ULTRA NEST COMBO KILL",
                        Awesomeness = 69, //thanks, Bill & Ted, real mature.
                        NestedArray = new []
                            {
                                new TestObject
                                    {
                                        Name = "nested array 1",
                                    },
                                new TestObject
                                    {
                                        Name = "nested array 2"
                                    }
                            }
                    }
                }
            })

 

The How

Well, the simplest way to see how is to check out the code, of course. I’m going to talk about some high level parts here, but suggest you take a look at the code to see it all working together in action. The meat of it is simply in checking the Body property of the expression. For a simple initializer, it’ll be MemberInitExpression.

var memberInitExpression = matchFunc.Body as MemberInitExpression;

From there, you can use the helper function off the Expression class to turn the expression into a lambda, compile it, and execute it. Then you have your expected value:

var expected = Expression.Lambda<Func<object>>(memberInitExpression).Compile()();

Now, since we don’t want to compare the entire value, only the specified properties, we can loop through the Bindings property of the MemberInitExpression. If this is another expression, we call ourselves with recursion. Otherwise, we use reflection and just test the values. Here’s the code snippet of us checking to see if we need to use recursion or not:

var bindingAsAnotherExpression = memberBinding as MemberAssignment;
 
if (bindingAsAnotherExpression != null &&
    bindingAsAnotherExpression.Expression.NodeType == ExpressionType.MemberInit)
{
      ShouldMatch(actualValue, bindingAsAnotherExpression.Expression as MemberInitExpression);
}

 

We want to handle arrays as well! Turns out, this is simple too. Just like we tested the Body property to see if it was an MemberInitExpression, we can test if it’s a NewArrayExpression.

var newArrayExpression = matchFunc.Body as NewArrayExpression;

Then from there, we just loop off the Expressions array property on the NewArrayExpression type, calling ourselves with recursion.

var array = actual.ToArray();
for (int i = 0; i < arrayExpression.Expressions.Count; i++)
{
    ShouldMatch(array[i], arrayExpression.Expressions[i] as MemberInitExpression);
}

Put those bits all together, and we have a pretty simple class that makes our tests a lot easier to read and maintain. I really do recommend checking out the finished class if you want to see how it all gels together!

 

TL;DR

Don’t use ShouldLookLikePartial anymore. Use ShouldLookLike! You can now have your cake and eat it too!

If you find any bugs, please report them at the GitHub SpecsFor repository! Also please let us know if there’s some functionality that ShouldLookLikePartial used to perform that ShouldLookLike does not.

Designed by Posicionamiento Web | Bloggerized by GosuBlogger