For whatever reason, developers don’t universally love the Visual-Studio-generated Accessor classes, as helpful as they may be. They do solve a lot of problems when you’re writing unit tests, though. It would be nice if you could achieve the same results without having to resort to an external class. Now, through the magic of reflection, and extension methods, you can.
The idea started simply enough. There are a number of places in my current project where developers (me included) have made methods public that really ought to be private or protected, just so that we can get to them for testing purposes. Then, we’ve simply excluded those methods from the public interface that we are using to reference those objects. We’re using StructureMap to handle dependency injection, so virtually every logic class, service, and controller is being used via an interface. In our particular case, this works well, but it has still always bugged me that we’re changing the behavior of classes to facilitate testing, and then depending on a condition of our environment to make that "okay". I wanted to keep those private members private, and just use reflection in the unit tests to get to the "naughty bits" that we shouldn’t be exposing, but I wanted to do it without the Accessor classes that so many people seem to despise.
What I wanted was to be able to get and set private fields and properties without having to go through a lot of extra Accessor class generation, and in a way that’s not going to clog up my unit tests with a lot of tedious reflection code. By writing a few extension methods against object (I know, I know), I can now write unit tests that look like this:
[TestMethod] public void SetPrivatePropertyValue_sets_property_value() { Target.SetPropertyValue("ReadOnlyProperty", "test"); string value = Target.ReadOnlyProperty; Assert.AreEqual("test", value); }
So far I’ve got extensions to get/set fields and properties, and to call non-public methods. Unfortunately, I can’t seem to get away from having to name the properties as "magic strings". These extensions aren’t meant for "real" day-to-day coding anyway, though. They’re meant to be used from unit tests, and if you rename a property or method in your code, the unit tests should barf the next time you run them anyway, so any problems shouldn’t live long. I’ve read some interesting information on Chad Meyers blog, but haven’t been able to make it work with my extension methods so far, or at least not without things getting messy.
Here is my current set of reflection extension methods:
public static class ReflectionHelper { private const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static; #region Field operations public static FieldInfo GetFieldInfo<TClass>(string name) { FieldInfo fieldInfo = typeof(TClass).GetField(name, bindingFlags); return fieldInfo; } public static object GetFieldValue<TClass>(this TClass instance, string fieldName) where TClass : class { if (instance == null) throw new ArgumentNullException("instance"); return GetFieldInfo<TClass>(fieldName).GetValue(instance); } public static void SetFieldValue<TClass>(this TClass instance, string fieldName, object value) where TClass : class { if (instance == null) throw new ArgumentNullException("instance"); instance.GetType().GetField(fieldName, bindingFlags).SetValue(instance, value); } #endregion #region Property operations public static PropertyInfo GetPropertyInfo<TClass>(string name) { PropertyInfo result = typeof(TClass).GetProperty(name, bindingFlags); return result; } public static object GetPropertyValue<TClass>(this TClass instance, string propertyName) where TClass : class { if (instance == null) throw new ArgumentNullException("instance"); return GetPropertyInfo<TClass>(propertyName).GetValue(instance, null); } public static object GetPropertyValue<TClass>(this TClass instance, string propertyName, object[] index) where TClass : class { if (instance == null) throw new ArgumentNullException("instance"); return GetPropertyInfo<TClass>(propertyName).GetValue(instance, index); } public static void SetPropertyValue<TClass>(this TClass instance, string propertyName, object value) where TClass : class { if (instance == null) throw new ArgumentNullException("instance"); instance.GetType().GetProperty(propertyName, bindingFlags).SetValue(instance, value, null); } public static void SetPropertyValue<TClass>(this TClass instance, string propertyName, object value, object[] index) where TClass : class { if (instance == null) throw new ArgumentNullException("instance"); instance.GetType().GetProperty(propertyName, bindingFlags).SetValue(instance, value, index); } #endregion #region Method operations public static MethodInfo GetMethodInfo<TClass>(string methodName) { MethodInfo result = typeof(TClass).GetMethod(methodName, bindingFlags); return result; } public static object CallMethod<TClass>(this TClass instance, string methodName) where TClass : class { if (instance == null) throw new ArgumentNullException("instance"); return instance.GetType().GetMethod(methodName, bindingFlags).Invoke(instance, null); } public static object CallMethod<TClass>(this TClass instance, string methodName, params object[] parameters) where TClass : class { if(instance==null) throw new ArgumentNullException("instance"); return instance.GetType().GetMethod(methodName, bindingFlags).Invoke(instance, parameters); } #endregion }
I’ll probably end up adding more later, but this is fine for my current needs.
Thanks! I was just looking at another post that had similar code (http://http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx) when I came across yours. The other post doesn\’t use extension methods. Yours looks cleaner.However, I\’m confused about why you\’re using "BindingFlags.Static" when it seems you only really need "BindingFlags.Instance" (as your extension methods all take an "instance" parameter). This also brings up the point of how to call static properties or methods. Is it possible to use extension methods for those too?Thanks again.
The BindingFlags.Static is just force of habit, I suppose. I\’ve taken it out of my local copy since its not accomplishing anything.