Unit testing in Silverlight part 4, the UI

Part 1
Part 2
Part 3
Part 4

Using the Silverlight Unit Test Framework it isn’t just possible to unit test regular code we we can also start to unit test user interface controls we create. Doing so is very similar to the asynchronous unit test we created before, just like those test we derive our test class from the SilverlightTest base class and decorate our test both with the Asynchronous and the TestMethod attribute.

First we need a user interface control to test. Just for demonstration purposes I created a very simple control to add URL’s to a collection. The XAML looks like this:

<UserControl x:Class="SilverlightApplication1.MyControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <StackPanel>
        <TextBox x:Name="txtPhotoUrl" TextChanged="txtPhotoUrl_TextChanged" />
        <Button x:Name="cmdAdd" Click="cmdAdd_Click" Content="Add" IsEnabled="False" />
    </StackPanel>
</UserControl>


And the code behind class like this:



using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
 
namespace SilverlightApplication1
{
    public partial class MyControl : UserControl
    {
        public MyControl()
        {
            InitializeComponent();
            Photos = new List<string>();
        }
 
        public IList<string> Photos { get; set; }
 
        private void cmdAdd_Click(object sender, RoutedEventArgs e)
        {
            string url = txtPhotoUrl.Text;
            Photos.Add(url);
        }
 
        private void txtPhotoUrl_TextChanged(object sender, TextChangedEventArgs e)
        {
            string url = txtPhotoUrl.Text ?? "";
            Uri uri = new Uri(url);
            
            if (string.IsNullOrEmpty(url))
                cmdAdd.IsEnabled = false;
            else if (string.IsNullOrEmpty(uri.Scheme))
                cmdAdd.IsEnabled = false;
            else if (uri.Scheme != "http")
                cmdAdd.IsEnabled = false;
            else
                cmdAdd.IsEnabled = true;
        }
    }
}


 







The code is a bit of an oversimplification but good enough to show how to unit test this control. Basically the textbox lets us add a new photo URL and when a valid photo URL has been entered the command button should be enabled so it can be added to the collection. What is a valid photo URL? Well simply any URL starting with http: [:)]



image



Our first test



The first test is to create the control and make sure the command button is enabled when a valid photo URL is entered. Like in the previous posts we need to create a Silverlight test project and set it up to use the Silverlight Unit Test Framework. See the previous posts on how to do that.



Next we need to make sure out test class derived from Microsoft.Silverlight.Testing.SilverlightTest. This is the class that will allow us to do asynchronous work and add controls to the test surface.



Adding the control to the test surface is easy. As I am using the control in every test I did so in a setup method decorated with the TestInitialize attribute as follows.



[TestClass]
public class Test1 : Microsoft.Silverlight.Testing.SilverlightTest
{
    private MyControl _myControl;
 
    [TestInitialize]
    public void TestSetup()
    {
        _myControl = new MyControl();
        TestPanel.Children.Add(_myControl);
    }


The test to actually add a photo to the collection looks like this:



[TestMethod]
[Asynchronous]
public void TestWeCanAddAValidUrl()
{
    TextBoxAutomationPeer textBoxPeer = new TextBoxAutomationPeer(_myControl.txtPhotoUrl);
    IValueProvider valueProvider = (IValueProvider)textBoxPeer;
    ButtonAutomationPeer buttonPeer = new ButtonAutomationPeer(_myControl.cmdAdd);
    IInvokeProvider buttonInvoker = (IInvokeProvider)buttonPeer;
 
    EnqueueCallback(() => valueProvider.SetValue("http://farm4.static.flickr.com/3085/3092376392_dc1aaf9eb6.jpg"));
    EnqueueConditional(() => buttonPeer.IsEnabled());
    EnqueueCallback(() => buttonInvoker.Invoke());
 
    EnqueueTestComplete();
}


There are a number of interesting things to note here. First of all I need to get a reference to the textbox and button control. These are declared as internal so by default I cannot get to them. However my adding the [assembly: InternalsVisibleTo("SilverlightTest")] attribute to the original project the test project will be able to see internal memebers as well as public members. Thus we can see the two controls.



Next I use the AutomationPeer objects to interact with the controls instead of doing so directly. These AutomationPeer objects are intended for alternative user interaction, for example a Braille screen reader, and allows us to mimic the UI better than by just calling the same method. For example I could just call the cmdAdd_Click() function if I hade made this public or internal but this doesn’t check if the button is enabled or not. The ButtonAutomationPeer will check and throw a ElementNotEnabledException if the button is not enabled.



Another thing to note is that I am using the asynchronous EnqueueCallback() and EnqueueConditional() methods to do the actual work. Finally I use the EnqueueTestComplete() function to end the test.



 



Adding a negative test



Just like the previous test produced a positive result we want to check if an invalid URL stops us from clicking the button and adding the URL to the list. This can be done using the following code.



[TestMethod]
[Asynchronous]
[ExpectedException(typeof(ElementNotEnabledException))]
public void TestTheButtinIsDisabledWithALocalUrl()
{
    TextBoxAutomationPeer textBoxPeer = new TextBoxAutomationPeer(_myControl.txtPhotoUrl);
    IValueProvider valueProvider = (IValueProvider)textBoxPeer;
    ButtonAutomationPeer buttonPeer = new ButtonAutomationPeer(_myControl.cmdAdd);
    IInvokeProvider buttonInvoker = (IInvokeProvider)buttonPeer;
 
    EnqueueCallback(() => valueProvider.SetValue(@"file:c:\images\3092376392_dc1aaf9eb6.jpg"));
    EnqueueCallback(() => buttonInvoker.Invoke());
}


Again like the previous time I am setting the textbox to a photo URL. Only in this case we have entered a local URL with the FILE: scheme instead of the required HTTP: scheme so the add button should remain disabled. We can test this by just invoking the button and specifying in test that it should expect an ElementNotEnabledException during the execution.



Conclusion



Unit testing UI elements is always hard and takes a lot of maintenance so something that is usually not the best way to test your complete application. However using the Silverlight Unit Test Framework  it is quite doable for small controls and I guess a unit test should be about testing a small thing anyway [:)]



Enjoy!

One thought on “Unit testing in Silverlight part 4, the UI

  1. Hi, Maurice.

    Thank you for your articles about Unit Testing in Silverlight. They are very usefull.

    Can you help me to resolve one problem?

    Here it is:
    In my test project I have general tests (in classes marked with [TestClass] attribute) and one test for UI (in class derived from SilverlightTest). If I run this project, then right panel, where we can see tests results, show information about this UI Test and nothing about general tests.
    If I remove this UI test, then right panel will show results for another (general) tests.

    What I’m doing wrong?

    Thanks again.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>