Mirror

Sometimes, the most obvious way to write a test is by accessing non-public state or behaviors. It may not be the best way to write the test but it may seem like the simplest solution for the time being.

MbUnit provides a couple of classes to help access non-public members.

Caveat: We don’t actually recommend testing non-public members because it can produce to brittle, unclear and unmaintainable tests. Please don’t shoot yourself in the foot with this feature…

API Documentation: Mirror class

Background

Mirror is a replacement for the MbUnit v2 Reflector class. It makes it easy to access non-public members, register non-public event handlers, and instantiate non-public types.

Using Mirror

The basic idea is that to access the non-public members of an object or type, you first create a Mirror instance using a factory method like Mirror.ForObject or Mirror.ForType. Accordingly there are two “kinds” of mirrors:

  • object mirrors provide access to instance or static members of a particular object instance.
  • type mirrors provide access to static members of a type.

Null Mirror

Mirrors can also represent null. Use Mirror.NullOfUnknownType or Mirror.ForObject(null) to obtain a null mirror.

Member Selection

After creating the Mirror, you need to select a member to access. This is done in one of the following ways.

  • Accessing a named member:
Mirror.ForObject(myInstance)["MemberName"]
  • Accessing a constructor:
Mirror.ForObject(myInstance).Constructor
  • Accessing a static constructor:
Mirror.Type<MyType>().StaticConstructor

Each of these properties returns a MemberSet object. A member set basically represents a collection of members that have the same name.

Working with MemberSet

What you do depends on what kind of member you're working with.

  • Get a field value:
object x = Mirror.ForObject(myInstance)["MyField"].Value;
  • Set a field value:
Mirror.ForObject(myInstance)["MyField"].Value = "abc";
  • Get a property value:
object x = Mirror.ForObject(myInstance)["MyProperty"].Value;
  • Set a property value:
Mirror.ForObject(myInstance)["MyProperty"].Value = "abc";
  • Get an indexed property value:
object x = Mirror.ForObject(myInstance)["MyProperty"][index].Value;
  • Set an indexed property value:
Mirror.ForObject(myInstance)["MyProperty"][index].Value = "abc";
  • Invoke a method:
object x = Mirror.ForObject(myInstance)["MyMethod"].Invoke(1, 2, 3);
  • Construct an object:
object x = Mirror.ForType<MyType>().Constructor.Invoke(1, 2, 3);
  • Add an event handler:
Mirror.ForObject(myInstance)["MyEvent"].AddHandler((sender, e) => HandleEvent(e));
  • Remove an event handler:
Mirror.ForObject(myInstance)["MyEvent"].RemoveHandler(handler);

Overload Resolution

Mirror tries to be smart about handling overloaded members. For example, when invoking an overloaded method, it will use the most specific overload that supports the parameters provided. Similarly Mirror tries to guess generic type arguments based on the parameters.

Unfortunately, sometimes Mirror needs a little help identifying the specific member to access. For that purpose you can provide additional details to the mirror to help narrow down the set of members.

Mirror.ForObject(myInstance)["MyMethod"]
      .WithBindingFlags(BindingFlags.NonPublic | BindingFlags.Instance)
      .Invoke("abc");
  • Specify parameter signature:
Mirror.ForObject(myInstance)["MyMethod"]
      .WithSignature(typeof(int), typeof(string))
      .Invoke(1, "abc");
  • Specify generic type arguments:
Mirror.ForObject(myInstance)["MyMethod"]
      .WithGenericArgs(typeof(int), typeof(string))
      .Invoke(1, "abc");

These hints can also be chained if needed.

Really Cool Things

Mirror lets you register an event handler without actually having to create an instance of the event handler delegate type. If the delegate that you provide has a compatible signature then Mirror will automatically coerce the delegate to the required type. This is very useful when the event handler type is itself non-public.

Longer Example

// A sample object with various members including a private nested class
// that has a public yet inaccessible static method.
public class SampleObject
{
  private int privateField;
  private int PrivateProperty { get; set;}
  private event EventHandler PrivateEvent;
  private int PrivateMethod(int a, int b) { ... }
 
  private static class PrivateNestedClass
  {
    public int StaticMethod(object y) { ... }
 
    public int OverloadedMethod(object y) { ... }
 
    public int OverloadedMethod(string y) { ... }
 
    public T GenericMethod<T>(T x) { ... }
  }
}
 
internal class InternalObject { ... }
 
internal delegate int InternalDelegate(InternalObject x);
// A sample test fixture.
public class Fixture
{
  [Test]
  public void Test()
  {
    // Setup.
    SampleObject obj = new SampleObject();
    Mirror objMirror = Mirror.ForObject(obj);
 
    // Reading from a private field.
    int privateFieldValue = (int) objMirror["privateField"].Value;
 
    // Writing to a private property.
    objMirror["PrivateProperty"].Value = 42;
 
    // Obtaining a mirror for a value at index 5 in a private indexed property.
    Mirror valueMirror = objMirror["Item"][5].ValueAsMirror;
 
    // Invoking a private method.
    int result = (int) objMirror["PrivateMethod"].Invoke(1, 2);
 
    // Accessing a private type.
    Type privateNestedClass = objMirror["PrivateNestedClass"].Type;
    Mirror privateNestedClassMirror = Mirror.ForType(privateNestedClass);
 
    // Invoking a static method.
    int result2 = privateNestedClassMirror["StaticMethod"].Invoke(42);
 
    // Invoking an overloaded static method when we cannot determine the parameter
    // type automatically from the arguments, such as when an argument is null.
    int result2 = privateNestedClassMirror["OverloadedMethod"].WithSignature(typeof(object)).Invoke(null);
    int result3 = privateNestedClassMirror["OverloadedMethod"].WithSignature(typeof(string)).Invoke(null);
 
    // Invoking a generic method.
    int result3 = privateNestedClassMirror["GenericMethod"].Invoke(42);
 
    // Invoking a generic method using specific type arguments.
    object result4 = privateNestedClassMirror["GenericMethod"].WithTypeArgs(typeof(object)).Invoke(42);
 
    // Creating an instance of an internal object.
    Mirror internalObjTypeMirror = Mirror.ForType("My.Namespace.InternalObject", "MyAssembly");
    object internalObj = internalObjTypeMirror.CreateInstance();
 
    // Creating an instance of an internal delegate.
    // Notice that the Mirror automatically adapts the provided delegate signature
    // to the actual delegate signature to make it easier to write tests when the delegate
    // parameter or result types are themselves inaccessible.
    Mirror internalDelegateTypeMirror = Mirror.ForType("My.Namespace.InternalDelegate", "MyAssembly");
    Delegate internalDelegate = internalDelegateTypeMirror.CreateDelegate(
      new Func<object, int>(x => { return 5; }));
  }
}