How To Unit Test Avalon / Windows Presentation Foundation
WindowRunner helps you launch a window on a separate thread and then stuff your content into it:
namespace
UnitTestingAvalon
{
using System;
public class WindowRunner
{
#region Constructor
internal WindowRunner(RetrieveWindow windowCallback, CreateContent contentCreator, object contentInformation)
{
if (windowCallback == null) { throw new ArgumentNullException("windowCallback"); }
this.WindowCallback = windowCallback;
if (contentCreator == null) { throw new ArgumentNullException("contentCreator"); }
this.ContentCreator = contentCreator;
this.ContentInformation = contentInformation;
}
#endregion Constructor
#region WindowCallback
internal delegate void RetrieveWindow(System.Windows.Window Window);
private RetrieveWindow WindowCallback { get { return this.windowCallback; } set { this.windowCallback = value; } }
private RetrieveWindow windowCallback;
#region WindowCallback
#region ContentCreator
internal delegate System.Windows.FrameworkElement CreateContent(object contentInformation);
private CreateContent ContentCreator { get { return this.contentCreator; } set { this.contentCreator = value; } }
private CreateContent contentCreator;
#endregion ContentCreator
#region ContentInformation
private object ContentInformation { get { return this.contentInformation; } set { this.contentInformation = value; } }
private object contentInformation;
#endregion ContentInformation
#region Run
internal void Run()
{
System.Windows.Window avalonWindow = new System.Windows.Window();
avalonWindow.SizeToContent = System.Windows.SizeToContent.WidthAndHeight;
avalonWindow.Content = this.ContentCreator(this.ContentInformation);
this.WindowCallback(avalonWindow);
avalonWindow.ShowDialog();
}
#endregion Run
}
}
The content creator method might look like this:
protected System.Windows.FrameworkElement CreateSurveyorControlFor(object surveyDefinition)
{
Surveyor surveyorControl = new Surveyor();
return surveyorControl;
}
ApplicationUnderTest drives all this:
namespace
UnitTestingAvalon
{
using System;
using System.Threading;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Threading;
internal static class ApplicationUnderTest
{
#region ApplicationThread
private static Thread ApplicationThread { get { return ApplicationUnderTest.applicationThread; } set { ApplicationUnderTest.applicationThread = value; } }
private static Thread applicationThread;
#endregion ApplicationThread
#region HostWindow
internal static Window HostWindow { get { return ApplicationUnderTest.hostWindow; } set { ApplicationUnderTest.hostWindow = value; } }
private static Window hostWindow = null;
#endregion HostWindow
#region Initialize / Deinitialize
internal static void Initialize(WindowRunner.CreateContent contentCreator, object contentInformation)
{
ApplicationUnderTest.CreateHostWindow(contentCreator, contentInformation);
}
internal static void Deinitialize()
{
if (ApplicationUnderTest.HostWindow != null)
{
ApplicationUnderTest.Invoke(delegate { ApplicationUnderTest.HostWindow.Close(); });
ApplicationUnderTest.HostWindow = null;
}
ApplicationUnderTest.ApplicationThread.Join();
ApplicationUnderTest.ApplicationThread = null;
}
#endregion Initialize
#region Avalon Invoke
// Avalon requires all interaction with UI to happen on the UI's thread. This invoke simplifies doing so.
internal delegate void DispatcherInvokeCallback();
internal static void Invoke(DispatcherInvokeCallback callback)
{
ApplicationUnderTest.HostWindow.Dispatcher.Invoke(DispatcherPriority.Normal, callback);
}
#endregion Avalon Invoke
#region WaitForIdle
private static void WaitForIdle(int timeout)
{
if (timeout <= 0)
{
throw new ArgumentException("The timeout specified must be greater than zero.");
}
DispatcherOperationCallback operationCallback = new DispatcherOperationCallback(delegate { return null; });
ApplicationUnderTest.HostWindow.Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, new TimeSpan(0, 0, timeout), operationCallback, null);
}
#endregion WaitForIdle
#region CreateHostWindow
private static void CreateHostWindow(WindowRunner.CreateContent contentCreator, object contentInformation)
{
WindowRunner windowRunner = new WindowRunner(ApplicationUnderTest.RetrieveWindow, contentCreator, contentInformation);
ApplicationUnderTest.ApplicationThread = new System.Threading.Thread(new System.Threading.ThreadStart(windowRunner.Run));
// Avalon requires UI to run on STA threads.
ApplicationUnderTest.ApplicationThread.SetApartmentState(System.Threading.ApartmentState.STA);
ApplicationUnderTest.ApplicationThread.Start();
// Give up the rest of our timeslice so the application's thread gets scheduled. This is required on uniprocessor machines.
Thread.Sleep(0);
}
#endregion CreateHostWindow
#region RetrieveWindow
private static void RetrieveWindow(System.Windows.Window window)
{
ApplicationUnderTest.HostWindow = window;
}
#endregion RetrieveWindow
}
}
So here's the flow:
- Your Test Method Setup calls ApplicationUnderTest.Initialize, providing the method that will create your UI as well as any random information that method requires.
- ApplicationUnderTest.Initialize calls ApplicationUnderTest.CreateHostWindow.
- ApplicationUnderTest.CreateHostWindow creates a new WindowRunner instance and starts it running on a separate thread.
- WindowRunner.Run creates a new Avalon.Window.
- WindowRunner.Run stuffs the return value from your UI creation method into the Window.
- WindowRunner.Run calls back into ApplicationUnderTest to give it a reference to the Window.
- WindowRunner.Run shows the Window modally.
- ApplicationUnderTest.CreateHostWindow blocks until the Window appears (not shown here).
- ApplicationUnderTest.CreateHostWindow and then .Initialize return.
- Your test case executes.
- Your Test Method Teardown calls ApplicationUnderTest.Deinitialize.
- ApplicationUnderTest.Deinitialize closes the Window.
- ApplicationUnderTest.Deinitialize blocks until the Window's thread exits.
- ApplicationUnderTest.Deinitialize returns.
Comments
- Anonymous
September 14, 2005
I wrote last month about my travails in attempting to unit test an Avalon - I mean, Windows Presentation...