Introduction
What is development without unit testing?
For me, it is just chaos! You make a bug fix and you end up giving birth to another 3… Also if you don't have unit tests, refactoring becomes a risk and you end up saying "IF IT WORKS, DON'T TOUCH IT"...
When developing software using WPF, you will hit a brick wall when creating your first unit test. You will encounter the InvalidOperationException
curse. The NUnit test runner will show you the red light and tell you: InvalidOperationException
failed to set the specified COM apartment state. If you try to run a unit test with the TestDriven.Net
Visual Studio plugin, this will not happen since this tool runs in STA yet 99% of your build server does not... So we have to work around this issue!
The Cause of this Problem
The cause of this problem is that NUnit runs in an MTA while WPF must run in STA. I found the solution to this problem here. I used this class (with some minor changes) in my AvalonUnitTesting
library. Basically, to run unit tests that are WPF related, you can use a class called AvalonTestRunner
(in the AvalonUnitTesting.dll supplied with this article) and call the RunInSta
method passing a delegate
. This will auto magically switch the current thread apartment to STA for you… Strictly speaking, you will not switch the apartment state of the thread, but instead start a new thread in STA and run the delegate
inside that thread. An example of such a test with the AvalonTestRunner
would look like this:
[Test]
public void TestMainWindow()
{
AvalonTestRunner.RunInSTA(
delegate
{
MainWindow window = new MainWindow();
Assert.AreEqual(300, window.Height);
});
}
More Than Just an Assert
WPF is a very cool, flexible and powerful platform... I just felt like saying it:)
Anyway, back to unit testing... With WPF you can also test user interaction like, for example, a button click. I found a very interesting article on this here.
Basically, you can use AutomationPeers
to raise events that are normally raised when a user does something with your app. I did not add any support for this in my library, but maybe someday (if my wife lets me) I will...:)
Unit Testing DataBinding
When you say "WPF", the first thing that comes to mind is the DataBinding feature. I guess DataBinding is one of the strongest features in WPF. Yet when you have an error "OOPS" nothing happens and nothing works. The reason why this happens is that WPF catches the exception for us and logs the error in the Trace (silent, but deadly). In actual fact, when there is a binding error, you can see the error in the Visual Studio debug window which is the default trace listener.
It would be great if there was a way in which you can unit test databinding, wouldn't it? So I decided to dig into this and have a look at what one can do. I found a very good article on WPF tracing over here. This article shed some light on what is happening and how I can create a small class that can unit test databinding for me. I said, why don't I create a trace listener that can assert whenever a WPF databinding exception is raised. Basically this solution can make your XAML databinding testable! So I created a class that listens to these databinding warnings and asserts to show me that there is a problem in the XAML. Basically the class (AvalonTraceListener
) I implemented is just a TraceListener
that asserts when WPF logs an error. So in the WriteLine
method you override your Assert
.
public override void WriteLine(string message)
{
if (currentWindowBeingTested != null)
currentWindowBeingTested.Close();
Assert.Fail(message);
}
So the code to test a Window or a control for data binding would look something like this:
[Test]
public void TestDataBindingForControls()
{
AvalonTestRunner.RunInSTA(delegate
{
AvalonTestRunner.RunDataBindingTests(new MainWindow());
AvalonTestRunner.RunDataBindingTests(new UserControlTests());
});
}
The process of testing the Window/control is very simple. You pass an instance of the object that contains your XAML and the RunDataBindingTests
method of the AvalonTestRunner
will check if the control is a Window, if not it will wrap the control you passed in a Window control. When we have a Window instance, then we call the Show
method of the Window. This will cause all databinding to be initialized and if there are any errors, our TraceListener
will make sure to catch them and fail the unit test.
It is very important that your control is not using resources from the Application.Resources
or any other shared resource since that would cause the test to fail. For example, if you have a ResourceDictionary
that is merged with the Application.Resources
, the test will fail since that Resource
is not found. What I would suggest to do in this scenario is to overload the constructor of your Window/control and make sure you add the resource you need just for unit testing, i.e.:
public Window1() : this(false)
{}
public Window1(bool unitTesting)
{
if(unitTesting)
Resources.MergedDictionaries.Add(new BrushesResources());
InitializeComponent();
}
Then pass the instance for the unit test passing true
in the constructor -> new Window1(true)
.
It is very important that you merge the Resource
you need before calling InitializeComponents
since this will try to load your XAML. Once you have all this in place, there you have it. You can now start unit testing WPF DataBinding as well....
Download the library and have loads of WPF fun.
History
- 22nd December, 2007: Initial post