Running MSTest In An MTA


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:


[TestMethod]
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);
 
    WaitHandle.WaitAll(new WaitHandle[]{ar1.AsyncWaitHandle,
        ar2.AsyncWaitHandle, ar3.AsyncWaitHandle});
 
    // 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:


<apartmentState type=System.Threading.ApartmentState>
  <value__ type=System.Int32>1</value__>
</apartmentState>

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.

Comments (14)

  1. There was an internal discussion this week about the need to run unit tests under MTA threads, rather

  2. Ever done complex interop testing where you wanted to run as MTA? Needed to call WaitHandle.WaitAll?

  3. Ever done complex interop testing where you wanted to run as MTA? Needed to call WaitHandle.WaitAll?

  4. Kris says:

    What is the reason why the MSTest runs in STA by default? I can understand if it is a WinForms app, but for testing a library of code why does the test default to STA?

  5. ploeh says:

    Performance, they told me.

  6. Anup says:

    This post was very helpful to me.

    Thanks.

  7. Ben Szymkow says:

    This article was helpful to me. Thank you!

  8. Juanma says:

    Hi, i found your post via MSDN Forums.

    I set the configuration file like this:

    <?xml version="1.0" encoding="UTF-8"?>

    <TestRunConfiguration id="97f0ad10-8fae-403e-b242-ed18618a9b7c" name="TestConWCF" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2006"&gt;

    <Description>This is a default test run configuration for a local test run.</Description>

       <ExecutionThread apartmentState="1"/>  

    </TestRunConfiguration>

    But I still getting the same error:

    FatalExecutionEngineError was detected

    Message: The runtime has encountered a fatal error. The address of the error was at 0x79eb59e7, on thread 0x79c. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack.

    I’m working with VS 2008 and I don’t know what I’m doing wrong.

    Thanks

  9. ploeh says:

    Hi Juanma

    Thank you for your question.

    While I don’t know why you get that specific error, to verify my previous description I just loaded up my sample project in Visual Studio 2008 to see if I would have the same problem, which I don’t.

    My guess is that your problem isn’t specifically related to the execution thread’s apartment state, but I can’t be sure.

    If you’d like, I can send you my sample project so you can run it to give you an indication whether you are having a problem with your specific project or perhaps your VS installation or something else.

    In this case, drop me a line via the blog’s Contact form so I get your e-mail address, and I’ll send you the sample code.

  10. deepak says:

    after adding the line <ExecutionThread apartmentState="1" /> in testrunconfig file

    I am getting the following error :

    Invalid cast from ‘System.String’ to ‘System.Threading.ApartmentState’.

    I am using vs2008. let me know, if i am missing anything…

  11. ploeh says:

    Hi deepak

    As I responded to Juanma, I can send you my sample project so you can take a look at that. If you are interested, drop me a line via the blog’s Contact form.

  12. Hi Mark,

    Thanks for sharing. As I required granular ability to use MTA or STA within a test run I put together a simple helper class to achieve this: http://plainoldstan.blogspot.com/2008/09/run-unit-test-on-mta-thread-vsts-test.html

    Please let me know if there are drawbacks if you would have a spare minute.

  13. ploeh says:

    Hi Stanislav

    Thank you for sharing. I posted a more elaborate comment directly at your post 🙂