Derek Lakin

Testing Private Members in Visual Studio

I’m currently working on a CommandBehaviour class to enable me to fire commands in response to arbitrary routed events on elements that don’t natively support commands, inspired by Sacha Barber’s post WPF: Attached Commands.

Whilst trying to apply some unit tests to what I’m writing I came across the age old problem of how to test things which are private by design. Under normal circumstances I would probably have made them protected and written a TestableCommandBehaviour class in the test project that inherits from CommandBehaviour and exposes the protected members that I wanted to test.

However, in this particular instance, I’d already written the code so I used the Create Unit Tests feature in Visual Studio to generate some unit tests for me to get me started. What I didn’t realise before was that when you do this, it creates a “Test Reference”, which provides you with a private accessor that you use to access the private code*.

Here’s a cut down version of the CommandBehaviour class:

1: namespace DerekLakin.Libraries.Presentation 2: { 3: publicclass CommandBehaviour 4: { 5: publicstaticreadonly DependencyProperty CommandProperty = 6: DependencyProperty.RegisterAttached( 7: "Command", 8: typeof(ICommand), 9: typeof(CommandBehaviour), 10: new UIPropertyMetadata(null)); 11: 12: publicstaticreadonly DependencyProperty CommandParameterProperty = 13: DependencyProperty.RegisterAttached( 14: "CommandParameter", 15: typeof(object), 16: typeof(CommandBehaviour), 17: new UIPropertyMetadata(null)); 18: 19: publicstaticreadonly DependencyProperty EventNameProperty = 20: DependencyProperty.RegisterAttached( 21: "EventName", 22: typeof(string), 23: typeof(CommandBehaviour), 24: new UIPropertyMetadata(string.Empty, new PropertyChangedCallback(EventNameChanged))); 25: 26: privatestaticreadonly DependencyProperty CommandBehaviourProperty = 27: DependencyProperty.RegisterAttached( 28: "CommandBehaviour", 29: typeof(CommandBehaviour), 30: typeof(CommandBehaviour), 31: new UIPropertyMetadata(null)); 32: 33: privatereadonly WeakReference sourceElement; 34: private EventInfo eventInformation; 35: private Delegate targetDelegate; 36: 37: private CommandBehaviour() 38: { 39: } 40: 41: private CommandBehaviour(DependencyObject source) 42: { 43: this.sourceElement = new WeakReference(source); 44: } 45: ... 46: } 47: }
A `CommandBehaviour` instance is created when the `EventName` attached property is set and the `targetDelegate` member is set when the relevant event has been hooked. In my unit test, I wanted to check that this member was actually being set and here’s how I did it using the accessor:
1: [TestMethod] 2: [DeploymentItem("DerekLakin.Libraries.Presentation.dll")] 3: publicvoid RemoveEventHandlerTest() 4: { 5: Grid source = new Grid(); 6: source.SetValue( 7: CommandBehaviour.CommandProperty, 8: ApplicationCommands.Open); 9: source.SetValue( 10: CommandBehaviour.EventNameProperty, 11: "MouseLeftButtonUp"); 12: CommandBehaviour real = (CommandBehaviour) 13: source.GetValue( 14: CommandBehaviour_Accessor.CommandBehaviourProperty); 15: CommandBehaviour_Accessor target = 16: new CommandBehaviour_Accessor( 17: new PrivateObject(real)); 18: Assert.IsNotNull(target.targetDelegate); 19: target.RemoveEventHandler(); 20: Assert.IsNull(target.targetDelegate); 21: }
First, I create a `Grid` instance and set the `Command` and `EventName` attached properties on it. Next, I get the `CommandBehaviour` instance from the `Grid` instance by using the accessor’s `CommandBehaviourProperty` (which is normally private). Then, I create a `CommandBehaviour_Accessor` instance that wraps the `CommandBehaviour` instance by using the `PrivateObject` class. Finally, I use regular `Assert` statements against the accessor to check the private members. Job done! * For more information about testing private methods, see [How to: Test a Private Method](http://msdn.microsoft.com/en-us/library/ms184807.aspx "How to: Test a Private Method") on the MSDN web site.
This work is licensed under a [Creative Commons Attribution By license.](http://creativecommons.org/licenses/by/3.0/)