Nerdy tidbits from my life as a software engineer

Friday, April 11, 2008

Mocking Up WPF Controls for Rhino Mocks

I figured out how to mock up WPF controls so that they can be unit tested using mock frameworks like Rhino Mocks. The solution is actually fairly easy. It's the generation part of this that is going to prove to be a lot of work. As you will see in a minute...

The problem with using Rhino Mocks with WPF controls is that the controls must be mocked up as classes. One of the disadvantages of Rhino Mocks is that it does not allow you to make expectations on non-virtual methods. Since the WPF framework was not developed with Rhino Mock compatibility in mind, many of the properties that you would want to mock up are not virtual methods or properties. This means that you will inevitably run into problems when you create mocked instances of these controls, and it means that you have to do some fancy things so that you can mock these objects up and create expectations on them in your unit tests.

OK, so how do we do that? The way to do it is to define an interface that has all of the public properties and methods of your WPF control, and then create a simple mockable object which extends the default control and implements the interface. Since the base class will always have these properties and methods implemented, this class is simply a place-holder. All we will do is replace references to regular WPF controls with the place-holder mocks, which can then be references by their interface references.

Some UML will help illustrate this, using a ComboBox as an example (note that Visio doesn't let you define a C#-style property in its UML diagrams; SelectedIndex is, obviously, a reference to the SelectedIndex property of the Selector class):

ComboBoxMockUML

The namespaces can, of course, be whatever you want them to be. But my feeling is that any use of this technique should be systematic. At first I put these objects in the same namespace as the controls themselves, but for reasons you will see in a moment, this didn't work very well.

OK, so, now, how do we use these mock objects in practice? All we do is replace standard XAML, such as this:

<UserControl x:Class="MJB.Testing.WPF.Controls.MainWindowControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel Orientation="Horizontal">
        <ComboBox x:Name="mComboBox"
                  SelectedIndex="0">
            <ComboBoxItem>Item 1</ComboBoxItem>
            <ComboBoxItem>Item 2</ComboBoxItem>
            <ComboBoxItem>Item 3</ComboBoxItem>
            <ComboBoxItem>Item 4</ComboBoxItem>
        </ComboBox>
        <Button Click="OnOKButtonClicked">OK</Button>
    </StackPanel>
</UserControl>
...with this: 
<UserControl x:Class="MJB.Testing.WPF.Controls.MainWindowControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:mocks="clr-namespace:System.Windows.Controls.Mocks;assembly=MJB.Mocks"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel Orientation="Horizontal">
        <mocks:ComboBoxMock x:Name="mComboBox"
                            SelectedIndex="0">
            <ComboBoxItem>Item 1</ComboBoxItem>
            <ComboBoxItem>Item 2</ComboBoxItem>
            <ComboBoxItem>Item 3</ComboBoxItem>
            <ComboBoxItem>Item 4</ComboBoxItem>
        </mocks:ComboBoxMock>
        <Button Click="OnOKButtonClicked">OK</Button>
    </StackPanel>
</UserControl>

See that? Easy. The ComboBoxMock object still acts exactly like a normal ComboBox - except that now we have a way to access it by its abstraction, which means virtual function pointers. Now we can write our code so that it is mockable:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Mocks;

namespace MJB.Testing.WPF.Controls
{
    /// <summary>
    /// This contains the code for the MainWindowControl.
    /// </summary>
    public partial class MainWindowControl : UserControl
    {
        #region Constructor

        /// <summary>
        /// Initializes the window.
        /// </summary>
        public MainWindowControl()
        {
            InitializeComponent();
            this.ComboBox = mComboBox;
        }

        #endregion
        #region Public Properties

        /// <summary>
        /// Gets / sets the combo box.
        /// </summary>
        public virtual IComboBox ComboBox
        {
            get;
            set;
        }


        #endregion
        #region Event Handlers

        /// <summary>
        /// Called when the OK button is clicked.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnOKButtonClicked(object sender, RoutedEventArgs e)
        {
            // Do something...
        }

        #endregion

    }

}

And now, finally, we can write a unit test that can mock up the ComboBox and create some expectations on it (see the next post for info on how Visual Studio 2008 is able to generate proxy's that let you write unit tests against UI objects):

/// <summary>
///A test for OnOKButtonClicked
///</summary>
[TestMethod()]
[DeploymentItem("MJB.Testing.WPF.Controls.dll")]
public void OnOKButtonClickedTest()
{
    MainWindowControl_Accessor target = new MainWindowControl_Accessor();
    IComboBox comboBox = this.Mocks.CreateMock<IComboBox>();

    Expect.Call(comboBox.SelectedIndex).Return(1);
    comboBox.SelectedIndex = 2;
    this.Mocks.ReplayAll();

    target.ComboBox = comboBox;
    target.OnOKButtonClicked(null, null);
    this.Mocks.VerifyAll();

}

How awesome is that? Finally, we can unit test user interfaces and mock up their dependencies! So the preceding code can be used to test and validate the following event handler without having to actually display a Window, select an item, and press a button:

/// <summary>
/// Called when the OK button is clicked.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnOKButtonClicked(object sender, RoutedEventArgs e)
{
    this.ComboBox.SelectedIndex++;
}

Of course, there is one pretty big problem with being able to do this on a large scale: the mock objects and their interfaces do not exist. Who's going to go and hand-write all of these things themselves? Before any of this can be used in an official capacity, a program needs to be written that can auto-generate these objects.

That gives me an idea for a really awesome project.....

Tuesday, April 8, 2008

Unit Testing WPF UI's In VS.NET 2008

One of the largest disadvantages of test driven development is that it hasn't worked, so far anyway's, on GUI's. There's a bit of a problem, when you think about it, on how to test a user interface: the interface must be visible on the screen, and it must respond to user input. Many of the user controls and visual elements of a GUI won't even work unless they are visible on the screen. So, how are you supposed to test them?
Fortunately, Microsoft appears to have (somewhat) solved the problem. Now, I am still in the very early understanding of how this works. A few things that I have discovered so far:

  • Visual Studio allows you to write unit tests against an .exe. This is very nice. How many times have you had to purposefully move all of your code into a .dll just so that it can be tested? This makes the .exe assembly nothing more than a pass-through shell, which is fine, until you realize that it doesn't really make much sense to do it that way if it weren't for the unit testing.
  • Visual studio allows you to unit test WPF controls by creating 'accessors'. I have no idea how these accessors work. It appears as though visual studio generates a stub assembly for you, which in turn mirrors your implementation class and somehow passes through your method and property calls into the UI. The accessors allow you to instantiate your accessed class and call methods on it as if it were an actual instance of the class you are testing. This is how you are able to actually unit test your WPF UI.
OK, so how does this work? Let's take an example of a stupid control that does nothing:
<UserControl x:Class="MJB.Testing.WPF.Controls.MainWindowControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel Orientation="Horizontal">
        <ComboBox x:Name="mComboBox"
                  SelectedIndex="0">
            <ComboBoxItem>Item 1</ComboBoxItem>
            <ComboBoxItem>Item 2</ComboBoxItem>
            <ComboBoxItem>Item 3</ComboBoxItem>
            <ComboBoxItem>Item 4</ComboBoxItem>
        </ComboBox>
        <Button Click="OnOKButtonClicked">OK</Button>
    </StackPanel>
</UserControl>

We have a silly user control with a ComboBox and a Button. When the OK button is clicked, we will call the OnOKButtonClicked method:

using System.Windows;
using System.Windows.Controls;

namespace MJB.Testing.WPF.Controls
{
    /// <summary>
    /// This contains the code for the MainWindowControl.
    /// </summary>
    public partial class MainWindowControl : UserControl
    {
        #region Constructor

        /// <summary>
        /// Initializes the window.
        /// </summary>
        public MainWindowControl()
        {
            InitializeComponent();
        }

        #endregion
        #region Public Properties

        /// <summary>
        /// Gets / sets the combo box.
        /// </summary>
        public virtual ComboBox ComboBox
        {
            get
            {
                return mComboBox;
            }
            set
            {
                mComboBox = value;
            }
        }


        #endregion
        #region Event Handlers

        /// <summary>
        /// Called when the OK button is clicked.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnOKButtonClicked(object sender, RoutedEventArgs e)
        {
            //  TO DO...
        }

        #endregion

    }

}

So the implementation of the MainWindowControl class has an empty event handler and an accessor for the ComboBox object. I included the accessor so that I can, hopefully, mock up ComboBoxes using Rhino Mocks. With this done, I naively right-clicked the OnOKButtonClicked method and had Visual Studio generate a unit test for me in a separate assembly. This generated a whole bunch of files and assemblies, one of which is clearly important: the .accessor file in the 'Test References' folder, which contains some innocuous looking text:

MJB.Testing.WPF.Controls.dll
Desktop

What on earth does that mean? If you look at the properties on the generated .accessor file, it has a build action called "Shadow", which appears to generate an assembly with an '_Accessor' suffix. In this case, the assembly I'm testing is called MJB.Testing.WPF.Controls, so Visual Studio generated a shadow assembly called MJB.Testing.WPF.Controls_Accessor.dll. Inside that assembly is a class that, from meta-data, looks as follows:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Windows;
using System.Windows.Markup;

namespace MJB.Testing.WPF.Controls
{
    [Shadowing("MJB.Testing.WPF.Controls.MainWindowControl")]
    public class MainWindowControl_Accessor : BaseShadow, IComponentConnector
    {
        protected static PrivateType m_privateType;

        [Shadowing(".ctor@0")]
        public MainWindowControl_Accessor();
        public MainWindowControl_Accessor(PrivateObject __p1);

        [Shadowing("_contentLoaded")]
        public bool _contentLoaded { get; set; }
        public static PrivateType ShadowedType { get; }

        public static MainWindowControl_Accessor AttachShadow(object __p1);
        [Shadowing("InitializeComponent@0")]
        public void InitializeComponent();
        [Shadowing("OnOKButtonClicked@2")]
        public void OnOKButtonClicked(object sender, RoutedEventArgs e);
    }
}

Bizarre, huh? If you Google the ShadowingAttribute class on MSDN you get absolutely no documentation at all, which makes this sub-system even more mysterious. I'm sure I'll be able to dig something up on this if I look hard enough, but for now, I'm pretty perplexed as to how it's making all this stuff work.
In any case, Visual Studio generates a unit test for you, which in this example looks something like this:

/// <summary>
///A test for OnOKButtonClicked
///</summary>
[TestMethod()]
[DeploymentItem("MJB.Testing.WPF.Controls.dll")]
public void OnOKButtonClickedTest()
{
    MainWindowControl_Accessor target = new MainWindowControl_Accessor();
    object sender = null; // TODO: Initialize to an appropriate value
    RoutedEventArgs e = null; // TODO: Initialize to an appropriate value
    target.OnOKButtonClicked(sender, e);
    Assert.Inconclusive("A method that does not return a value cannot be verified.");
}

So my goal in this test was very simple: I want to test that when the user presses the OK button that the SelectedIndex of the ComboBox is increased by one. So I started doing things the old fashioned way using my much beloved mock framework:

/// <summary>
///A test for OnOKButtonClicked
///</summary>
[TestMethod()]
[DeploymentItem("MJB.Testing.WPF.Controls.dll")]
public void OnOKButtonClickedTest()
{
    MainWindowControl_Accessor target = new MainWindowControl_Accessor();

    ComboBox comboBox = this.Mocks.PartialMock<ComboBox>();                

    this.Mocks.BackToRecordAll();
    Expect.Call(comboBox.SelectedIndex).Return(0);
    comboBox.SelectedIndex = 1;
    this.Mocks.ReplayAll();    

    target.ComboBox = comboBox;
    target.OnOKButtonClicked(null, null);
    this.Mocks.VerifyAll();    

}

But, guess what? This doesn't work. I've tried everything, but it fails every time. No matter what combination of Partial / Dynamic / Regular mocking I do; no matter whether I extend the ComboBox class and try to mock that, or set expectations where you wouldn't normally want to set expectations, I get a variety of exceptions. I think the only way to really mock up objects like the ComboBox's to generate _Accessor classes for them as well, and then use mock frameworks to mock up the accessors and set up expectations on them. So I will need to figure out how to do that, which I will surely document in a separate blog post, if I figure it out.

So for now, I'm stuck testing the UI without the help of mock objects. Which is fine for simple examples like this one (but will get into lots of real problems when we start working with actual methods that do complicated things like generating commands and routed events):

/// <summary>
///A test for OnOKButtonClicked
///</summary>
[TestMethod()]
[DeploymentItem("MJB.Testing.WPF.Controls.dll")]
public void OnOKButtonClickedTest()
{
    MainWindowControl_Accessor target = new MainWindowControl_Accessor();

    target.ComboBox.SelectedIndex = 0;
    target.OnOKButtonClicked(null, null);
    Assert.AreEqual(1, 
        target.ComboBox.SelectedIndex, 
        "Did not increment the combo box!");            

}

This test will, in fact, successfully test our simple example method:

/// <summary>
/// Called when the OK button is clicked.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnOKButtonClicked(object sender, RoutedEventArgs e)
{
    this.mComboBox.SelectedIndex++;
}

...which is actually, really awesome. Now, at last, we can write solid unit tests against UI's without running into the whole 'it has to be displayed on the screen' problem. What's missing, however, are mock objects. So for simple tests with no dependencies, this will work fine. But until I figure out how to use Rhino mocks to separate dependencies, I'm not entirely sure how useful this is going to be. I'll keep plugging away at it and see what I can figure out - there has to be a way to do it, and I will figure it out if it kills me.