Writing deterministic, multi-threaded unit tests may not be your idea of a fun Saturday night, but with a little discipline and knowledge, it can be done, and it sure beats writing multi-dreaded code without the safety net provided by an automated test suite.
In this situation, synchronization becomes really important, and the various WaitHandle classes become some of your best friends. In many test cases, your SUT will probably be kicking off new threads behind the scenes, and you'll need to wait for whatever work they are performing before you carry out the test verification. Waiting for all work to complete sounds just like a job for WaitHandle.WaitAll:
public void WaitForAllDoStuff()
MyClass mc1 = new MyClass();
MyClass mc2 = new MyClass();
MyClass mc3 = new MyClass();
IAsyncResult ar1 = mc1.BeginDoStuff("ploeh", null, null);
IAsyncResult ar2 = mc2.BeginDoStuff("fnaah", null, null);
IAsyncResult ar3 = mc3.BeginDoStuff("ndøh", null, null);
// Asserts go here...
In this example, MyClass exposes the DoStuff operation using the Async pattern (or should we say, coding idiom). After waiting for all the processing to complete, the test can gather results by calling EndDoStuff and subsequently perform asserts (not shown). There are other ways to do this, but sometimes, WaitHandle.WaitAll is a nice and convenient construct.
The only problem is that when you try to run this test, it fails with the following error message: Test method Ploeh.Samples.MyLibraryTest.MyClassTest.WaitForAllDoStuff threw exception: System.NotSupportedException: WaitAll for multiple handles on a STA thread is not supported..
This is actually documented behavior on the part of WaitHandle.WaitAll. What may come as a surprise, though, is that MSTest runs in an STA by default.
Fortunately, this is configurable and can be edited in your .testrunconfig file. There's no user interface for this setting, so you will have to open the file with your favorite XML editor and change this element:
By default, the value of this element is 0 (which is equivalent to ApartmentState.STA - the default value of the enumeration), but as you can see, I've changed it to 1 (which is equivalent to ApartmentState.MTA). After saving this change, the test now succeeds.
In Visual Studio 2008, the format of the .testrunconfig file has changed, and this setting is not present at all by default. However, you can add it like this to achieve the same effect:
<ExecutionThread apartmentState="1" />
Other than that, it works in the same way.