Monday, October 27, 2008

Tricks for Writing Unit Tests for WPF Applications

Usually you write unit tests for non-UI related classes that only do background logics. But sometimes you can't avoid writing unit tests for UI related classes. These days I tried such kind of unit test. And there are some tricks that are useful while you are writing WPF unit tests I willing to share with you. Let's get started.

Get Your Window Ran

First thing first, you should get your window shown and ran. If your window is a child window of your whole application, then you have to create a fake parent window. And then build relationship between the testee window and the fake parent window.
Window fakeParentWindow = new Window();
Foo testee = new Foo();
fakeParentWindow.Content = testee; // if testee is a kind of UIElement
testee.Owner = fakeParentWindow; // if testee is a kind of Window
fakeParentWindow.Show();
testee.Show(); // if it's a Window

If the testee is a Window, you can let it be the child window of the fakeParentWindow by assign fakeParentWindow to testee's property Owner. Or just do nothing you will get a top window testee. Don't forget to call Show() if it is a Window.

That's not everything of initializing a stage for your unit test. There are at least two threads you may interested in
  • Main thread
  • The worker thread running as an agent for your unit test case (I don't know why it is called an agent thread)
As listed above, your test case will be running within a worker thread. Almost all thread time are dedicated to your test method if you don't carry out some special actions. If your test method blocks the worker thread, your windows won't get message pumped and events won't be handled, and windows even won't get rendered. To solve the problem, just call System.Windows.Threading.Dispatcher.Run() which will start pumping messages and get dispatcher works.
Dispatcher.CurrentDispatcher.BeginInvoke(
DispatcherPriority.Background,
new DispatcherOperationCallback(CarryOutTestSteps),
null);

System.Windows.Threading.Dispatcher.Run();

Since Dispatcher.Run() will block the test method, you have to invoke your test steps via CurrentDispatcher.BeginInvoke() in order to starting test. Your test steps will still be processed in the worker thread, but it will be processed as a dispatcher work item.

By now, your window is showing.

Don't put Dispatcher.Run() into TestInitialize(), because Dispatcher.Run() will also blocks the thread and your test method won't get the chance to run.

Quit the Unit Test

Soon you will find that the unit test won't quit even the dispatcher work item has been completed, and even you close the windows manually. The thread is still enjoying pumping messages. To tell it to stop doing stupid things, just call Dispatcher.ExitAllFrames() when you finished all test steps:
System.Windows.Threading.Dispatcher.ExitAllFrames();
Everything looks good, but... you will get an exception during the thread's termination. The exception says:

"An unhandled exception of type 'System.Runtime.InteropServices.InvalidComObjectException' occurred in PresentationCore.dll Additional information: COM object that has been separated from its underlying RCW cannot be used."

I searched on the Internet. Eventually I found that was a bug of .Net Framework. A work around came from Visual Studio Product Team is calling CurrentDispatcher.InvokeShutdown() from TestCleanup().
[TestCleanup]
public void TestCleanup()
{
System.Windows.Threading.Dispatcher.CurrentDispatcher.InvokeShutdown();
}
Be careful, .Net Framework may be changed letting this work around unnecessary.

References

  1. WPF / Visual Studio 2008 Unit Test Workaround
  2. Unit Tests Which Show Windows Cause VsTestHost/MsTest ShutDown Crashes in VS 2008 RTM