Unit Testing the User Interface
Using MVP and automatic data binding, unit tests for the view become straightforward. Because the view is now responsible for only visualization of values and responding to binding events, you can cover most of the functionality of the view with two unit tests for each display property:
- TestVisualizingPropertyX
- TestBindingPropertyX
In order to run a unit test for the view, it is necessary to launch the Form or Control, and inspect its state by inspecting the child controls within the Form or Control. Because child controls within a parent are typically represented by private member variables, you can leverage a Tester class that extracts the controls from the parent. In Listing Four, the GUITester class uses reflection to traverse the parent's hierarchy to retrieve the specified control by the control's Name property.
public static class GUITester { public static T FindControl<T>(Control container, string name) where T : Control { if (container is T && container.Name == name) return container as T; if (container.Controls.Count == 0) return null; foreach (Control child in container.Controls) { Control control = FindControl<T>(child, name); if (control != null) return control as T; } return null; } #endregion }
Once you can retrieve the child controls, it is straightforward to create a unit test to validate this visualization of a property. All you need to do is launch the form, update the property, and validate the state of the control; see Listing Five.
[TestFixture] public class CalculatorFormTest { [Test] public void TestVisualizingOperand2() { CalculatorForm target = new CalculatorForm(); target.Show(); TextBox operand2TextBox = GUITester.FindControl<TextBox>(target, "_operand2TextBox"); _target.Operand2 = 100; Assert.AreEqual(target.Operand2.ToString(), _operand2TextBox.Text); target.Close(); } }
Now that you have test coverage for the visualization, you need to ensure that binding works correctly. This can be achieved with a simple unit test validating that the model and view are kept in sync, as in Listing Six.
[TestFixture] public class CalculatorFormTest { [Test] public void TestBindingOperand2() { CalculatorForm target = new CalculatorForm(); Calculator model = target.Calculator(); target.Show(); // Update the UI and validate the model target.Operand2 = 7; Assert.AreEqual(target.Operand2, model.Operand2); // Update the model and validate the UI model.Operand2 = 9; Assert.AreEqual(model.Operand2, target.Operand2); target.Close(); } }
As long as you create unit tests of this type for each display property, you can add a significant amount of coverage with little unit test code.
Conclusion
By leveraging MVP and automatic data binding, you can create simple unit tests that cover most of the responsibilities of the view. While it is true that some view operations, such as drag-and-drop, are difficult to test, binding and visualization are at the heart of what is happening in a view.
In addition, you can view the wrapper properties used in the example as superfluous. Although it is not necessary to wrap each text box with its own display property, the example serves to demonstrate the simplicity of creating the one-to-one mapping between the presentation property and the view property. How you apply the rule to each new application is up to you.
As long as you adhere to the spirit of MVP, you will find a straightforward approach to unit testing the view.