GSharper

Monday 1 September 2008

Builder pattern

I was writing unit tests when I stumbled on a problem. I had a class TransitionInfo which I couldn't instantiate.

The constructor was made internal. Also, all the properties were getters, so I couldn't set any of them. So, there was only one thing todo: Reflection !!

The TransitionInfo object was used in more than 1 unit tests. In fact, it was used in alot of them. So I had to copy all this ugly reflection code in each unit test.

I couldn't do this of course. I hate copy pasting.

So I talked to our Architect on the team and he put me on the right track.

This is how I implemented the solution:

First I had to create a sort of builder class which was goin to encapsulate all of the reflection code. The purpose of the builder class was to give me a constructed TransitionInfo object.

This is the code:

internal class TransitionInfoBuilder
{
// Create our return object
private TransitionInfo expectedTransitionInfo;

public TransitionInfoBuilder()
{
Type type = typeof(TransitionInfo);
Type[] constructorParameterTypes = new Type[]
{ typeof(TransitionTrigger), typeof(string), typeof(int), typeof(string), typeof(string),
typeof(string) };

// Get the internal constructor
ConstructorInfo cInfo = type.GetConstructor(BindingFlags.Instance
BindingFlags.NonPublic, null,
CallingConventions.HasThis,
constructorParameterTypes,
null);

// Call invoke and instantiate the return object
expectedTransitionInfo = (TransitionInfo) cInfo.Invoke(new object[]
{ TransitionTrigger.After, "", 0, "", "", "" }); }


// Let's fill in some private field:
public TransitionInfoBuilder WithItemId(int itemId)
{
// Set the private field
SetTransitionField(itemId, "m_ItemId");

// Return a builder object
return this;
}

// Now we have to expose the transitionInfo object
public TransitionInfo Stub
{
get
{
return this.expectedTransitionInfo;
}
}

// Use reflection to set the private field

private void SetTransitionField(object fieldValue, string fieldName)
{
Type myType = typeof(TransitionInfo);
FieldInfo myFieldInfo = myType.GetField(fieldName, BindingFlags.NonPublic
BindingFlags.Instance);

myFieldInfo.SetValue(expectedTransitionInfo, fieldValue);
}

Now we can call the builder class and retrieve our stubbed TransitionInfo object

TransitionInfo transitionInfo = new TransitionInfoBuilder()
.WithItemId(1)
.Stub;

Greetz,
Glenn

comments are always welcome