Thursday, April 26, 2007

Unit Testing Private Methods

When writing unit tests, I frequently find myself wanting to test private methods. I used to think there were only three approaches to solving this problem.

  1. Test the private method indirectly through some other, public method that invokes it.
    • Pros: Keeps your class interface clean and doesn't compromise your OO design
    • Cons: You may not be able to test all of the cases you'd like through the public method, and your test results may be affected by the code in the public method.

  2. Make the private method protected and create a derived class in your test project that exposes the method publicly.
    • Pros: Allows you test the method directly
    • Cons: Compromises the encapsulation of your initial design, requires writing more test classes.

  3. Refactor the private method into some other class that exposes it publicly.
    • Pros: Doesn't require more test code, you may actually end up with more well-factored design
    • Cons: You probably don't want to make this code public, isn't that why it was private in the first place?
So, given these options, I went with #2. I've written a lot of tests like this, but it's a chore to implement these wrapper classes in your test code, and I always thought there must be a better way. Fortunately, if you're programming in .Net, there is:
  1. Use reflection to invoke the private method directly
    • Pros: Totally rocks.
    • Cons: Invoking the private method looks a little funky
Kudos to Michael Kelly for showing me this approach. Now that I've seen this in action, its the only way I deal with this case. Here's a sample implementation to get you started:

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

public class TestReflector
{
public static T InvokeNonPublicMethod<T>( string methodName, object obj,
params object[] parameters )
{
MethodInfo methodInfo =
obj.GetType().GetMethod( methodName, BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.Static);
if ( methodInfo == null )
{
throw new ApplicationException( "Private method not found for: '" +
methodName + "'." );
}
return (T)methodInfo.Invoke( obj, parameters );
}
}


With that in place, it's very simple to use:

MyClass myClass = new MyClass();
string string1 = "abcdef";
string string2 = "123456";
string result = TestReflector.InvokeNonPublicMethod<string>(
"MyPrivateMethod", myClass, string1, string2 );


This code will support any non-public method with any number of parameters and any return type, except void. If you do need void support, its a simple adaptation of this code. Hope this helps!

5 comments:

Chris Bilson said...

Hi John,

MbUnit now has support for testing private things, which makes it a little easier to read and do this type of testing.

It's funny because I just had this same conversation with my buddy at work today. I recommended #3 - I think most of the the time we feel something like this should be private because it's really in the wrong place - but you never know. What's more important is that we get that code under test and then worry about refactoring, and relfection is a perfectly valid way to do that quickly, IMHO.

John Hann said...

I didn't know that about MbUnit, thanks for sharing!

Vikram said...

John
Thanks for this post, it works beautifully for private methods.

How would you go about testing private methods that are overloaded? I tried using the OptionalParamBinding but that didn't work.

Thanks
Vik

Vikram said...

This seems to work, I was wrong about using the OptionalParamBinding

public static T InvokeNonPrivateOverloadedMethod &lt T &gt(string methodName, object obj, params object[] parameters)
{
object result = obj.GetType().InvokeMember(methodName, BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic, null, obj, parameters);
return (T)result;
}

John Hann said...

Nice, thanks for posting the solution!