Web Statistics

Thursday, May 21, 2009

Testing private members with NUnit

For those using MSTest, private accessors are created for you so that testing private members is trivial. If you’re using NUnit, however, there’s a bit more work involved.

First, repeat after me… Reflection is good. Reflection is good. Reflection is good.

We can use System.Type.GetMethod to get access to a methods metadata and then use System.Reflection.MethodBase.Invoke to execute the method.

So, given a class FooMaster with a private, static method Foo

    1 public class FooMaster

    2 {

    3     private static bool Foo(string myString, int myInt)

    4     {

    5         //init the result

    6         bool result = false;

    7 

    8         //does the length of string match the int passed in?

    9         if (myString != null)

   10         {

   11             result = myString.Length == myInt;

   12         }

   13         else if (myInt == 0)

   14         {

   15             result = true;

   16         }

   17 

   18         return result;

   19     }

   20 }


a unit test would look something like this
    1 [Test]
    2 public void Foo1()
    3 {
    4     System.Reflection.MethodInfo fooMethod =
    5         typeof(FooMaster).GetMethod("Foo",
    6             (System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static));
    7 
    8     object[] parameterValues = { "my test string", 14 };
    9 
   10     bool result = (bool)fooMethod.Invoke(null, parameterValues);
   11 
   12     Assert.IsTrue(result, "Foo did not return the expected result!");
   13 }

In this case, because FooMaster.Foo is a private, static method, we need to include the
System.Reflection.BindingFlags.NonPublic
and
System.Reflection.BindingFlags.Static
BindingFlags. Otherwise, the GetMethod call will throw a System.NullReferenceException exception.

The array of System.Object
object[] parameterValues = { "my test string", 14 };
contains the list of parameter values that we'll pass to the method when we execute it.

When executing with the Invoke method
bool result = (bool)fooMethod.Invoke(null, parameterValues);
we need to cast the result accordingly and then apply the appropriate Assert.

¡Hay Chimba! Life is good...until an overload is introduced. Now we need to do a little bit more work.

Introducing an overload of Foo that looks like this
    1 private static bool Foo(string myString, byte myByte)
    2 {
    3     return Foo(myString, Convert.ToInt32(myByte));
    4 }

will cause our unit test to throw a System.Reflection.AmbiguousMatchException exception since FooMaster now has two methods named Foo.

So, we need to update our unit test a bit to include an array of Type objects that represent the number, order, and type of parameters for the method that we're trying to get a reference to.
    1 [Test]
    2 public void Foo1()
    3 {
    4     System.Reflection.MethodInfo fooMethod =
    5         typeof(FooMaster).GetMethod("Foo",
    6             (System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static),
    7             null, new Type[] { typeof(string), typeof(int) }, null);
    8 
    9     object[] parameterValues = { "my test string", 14 };
   10 
   11     bool result = (bool)fooMethod.Invoke(null, parameterValues);
   12 
   13     Assert.IsTrue(result, "Foo did not return the expected result!");
   14 }
   15 
   16 [Test]
   17 public void Foo2()
   18 {
   19     System.Reflection.MethodInfo fooMethod =
   20         typeof(FooMaster).GetMethod("Foo",
   21             (System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static),
   22             null, new Type[] { typeof(string), typeof(byte) }, null);
   23 
   24     object[] parameterValues = { "my test string", (byte)14 };
   25 
   26     bool result = (bool)fooMethod.Invoke(null, parameterValues);
   27 
   28     Assert.IsTrue(result, "Foo did not return the expected result!");
   29 }

You can see here that our unit tests include an array of Type
new Type[] { typeof(string), typeof(int) }
and
new Type[] { typeof(string), typeof(byte) }

to futher specify which Foo method to get a reference to.

Very cool. Remember...Reflection is good.

0 comments:

Post a Comment