WatiN and Thread.ApartmentState

This article explains why you need to set the Thread.Apartmentstate to STA when using WatiN. If you use a (test) runner which is not mentioned on this page but which needs some special setup to set the ApartmentState to STA, please let me know so I can add the info to this page.

Index

  1. Why is setting the ApartmentState needed in the first place?
  2. Differences in ApartmentState between .Net 1.1 and .Net 2.0.
  3. Implications.
  4. Behaviour of WatiN when Thread is not an STA Thread.
  5. Using WatiN in a console or WinForms application.
  6. Using WatiN and MBUnit.
  7. Using WatiN and NUnit.
  8. Using WatiN and Visual Studio 2005 test runner.
  9. Using WatiN and TestDriven.Net in VS 2003 and VS2005.
  10. Using WatiN and SharpDevelop’s NUnit test runner.
  11. Using WatiN and Fitnesse.

Why is setting the ApartmentState needed in the first place?

Copied from MSDN:

“Because COM classes use apartments, the common language runtime needs to create and initialize an apartment when calling a COM object in a COM interop situation. A managed thread can create and enter a single-threaded apartment (STA) that allows only one thread, or a multithreaded apartment (MTA) that contains one or more threads.”.

Since Internet Explorer is not Thread safe we need to use STA.


Differences in ApartmentState between .Net 1.1 and .Net 2.0

Digging through MSDN I found out that Microsoft not only has changed the calling convention for setting or getting the ApartmentState, it also changed the default ApartmentState for a Thread in .Net 2.0. (see http://msdn.microsoft.com/netframework/programming/breakingchanges/runtime/clr.aspx and search for STAThreadAttribute).

In .Net 1.1 the default ApartmentState of a Thread is Unknown. A running Thread with this ApartmentState can be set to STA or MTA, but only once. Trying to set the Apartment state of a running Thread from MTA to STA (or vice versa) doesn’t have any effect, it remains MTA (or STA).

In .Net 2.0 the default ApartmentState of a Thread is MTA. Trying to reset the ApartmentState of a running Thread from MTA to STA (or vice versa) doesn’t have any effect, it remains MTA. Setting the ApartmentState to Unknown or STA instead of MTA, can ONLY be done BEFORE the Thread starts.


Implications

Using WatiN works fine in a console or GUI application when you apply the [STAThread] attribute to the main method (the sole entry point of the application). This way the main Thread runs as STA and all goes well. When using a runner like MBUnit, NUnit or any other runner that starts the main Thread, your code/tests depend upon the ApartmentState the runner started with.


Behaviour of WatiN when Thread is not an STA Thread.

WatiN 0.8.0 (and later):

Will throw a ThreadStateException when you create an instance of the WatiN.Core.IE class, to help you remind to set the ApartmentState to STA.

Earlier versions:

You will get an “Invalid cast exception” being thrown when you create an instance of the WatiN.Core.IE class.


Using WatiN in a console or WinForms application

When using WatiN from a console or WinForms application, you need to apply the [STAThread] attribute to the Main method (the entry point of the application).

using System;
using WatiN.Core;
 
namespace WatiNGettingStarted
{
 class WatiNConsoleExample
 {
  [STAThread]
  static void Main(string[] args)
  {
   // Your test code;
  }
 }
}

See the Getting Started article for a full console application example.


Using WatiN and MBUnit

Setting the ApartmentState for a MBUnit TestFixture is quit simple. Following is a basic example copied from http://www.mertner.com/confluence/display/MbUnit/FixtureThreadApartmentState.

[TestFixture(ApartmentState = ApartmentState.STA)]
public class WatiNTestFixture
{
 [Test]
 public void WatiNTest()
 {
   // Your test code;
 }
}


Using WatiN and NUnit

First you need to create a config file for your test assembly. The naming of this file depends on how you use NUnit (more info here):

  • If you load a test assembly directly, such as mytests.dll, the config file must be named mytests.dll.config located in the same directory mytests.dll is in.
  • If you load an NUnit project, such as mytests.nunit, the config file must be named mytests.config.
  • If you use NUnit’s Visual Studio support to load a Visual Studio project or solution, such as mytests.csproj or mytests.sln, the config file must be named mytests.config.

Next, paste the following XML into the config. The trick is in the <add key="ApartmentState" value="STA" /> part. nunit-gui and nunit-console will now run all your tests in the assembly on a single threaded apartment Thread.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
   <sectionGroup name="NUnit">
    <section name="TestRunner" type="System.Configuration.NameValueSectionHandler"/>
   </sectionGroup>
  </configSections>
  <NUnit>
   <TestRunner>
    <!-- Valid values are STA,MTA. Others ignored. -->
    <add key="ApartmentState" value="STA" />
   </TestRunner>
  </NUnit>
</configuration>

Following is some background information about the differences between the behaviour of NUnit  for .Net 1.1. and .Net 2.0 (keep the explanation about  “Differences in ApartmentState between .Net 1.1 and .Net 2.0” in mind).

To run the unit tests, NUnit creates Threads with the default ApartmentState. So if you use WatiN in a NUnit .Net 1.1 environment, a Thread with the ApartmentState Unknown is created. This makes it possible to set the ApartmentState (only ones) to STA. You could do this, for instance, in your TestFixtureSetup function by adding the following line of code:

System.Threading.Thread.CurrentThread.ApartmentState = System.Threading.ApartmentState.STA;

But Using WatiN in a NUnit .net 2.0 environment, this code doesn’t work, even if you make the syntax .Net 2.0 compliant:

System.Threading.Thread.Currentthread.SetApartmentState(System.Threading.ApartmentStat.STA);

This is caused by the fact that NUnit now creates a Thread with the ApartmentState set to MTA (the .Net 2.0 default value).

So my advise: if you develop in a .Net 1.1 environment, use the config file to force the right ApartmentState. This will give you a hassle free migration to the .Net 2.0 framework (regarding the use of WatiN).


Using WatiN and Visual Studio 2005 test runner.

No special tricks needed. The Visual Studio 2005 Team System test runner runs all it’s tests in STA mode by default.


Using WatiN and TestDriven.Net in VS 2003 and VS 2005.

No special tricks needed. TestDriven.Net runs all it’s tests in STA mode by default.
When TestDriven.Net invokes MbUnit tests, MbUnit overrides the ApartmentState, setting it to MTA by default. So you do need to specify the ApartmentState in the fixture attribute:

[TestFixture(ApartmentState = ApartmentState.STA)]

See also using WatiN and MBUnit.


Using WatiN and SharpDevelop’s NUnit test runner.

As of SharpDevelop 2.1 Beta 3, running test in STA mode is supported. By default SharpDevelop runs tests in the MTA. This is the default behaviour of NUnit which is what SharpDevelop uses. To run your tests in an STA you can create an app.config file for your test project as shown below:

<configuration>
 <configSections>
  <sectionGroup name="NUnit">
   <section name="TestRunner" type="System.Configuration.NameValueSectionHandler" />
  </sectionGroup>
 </configSections>
 <NUnit>
 <TestRunner>
  <!-- Valid values are STA,MTA. Others ignored. -->
  <add key="ApartmentState" value="STA" />
 </TestRunner>
 </NUnit>
</configuration>

Using WatiN and FitNesse.

To use WatiN with FitNesse, you have two options:

  • Write WatiN code in your Fixtures.
  • Use WatiNFixture, written and maintained by Jeff Parker.

In both situations you need to modify and recompile the .Net fitserver to run your tests in an STA.

Open de main.cs file of the FitServer project and apply the STAThread attribute to the main methode. The code will then look like this:

// Modified or written by Object Mentor, Inc. for inclusion with FitNesse.
// Copyright (c) 2002 Cunningham & Cunningham, Inc.
// Released under the terms of the GNU General Public License version 2 or later.
using System.Threading;
namespace FitServer
{
  public class FitServerMain
  {
    [STAThread]
    public static int Main(string[] CommandLineArguments)
    {
      fitnesse.fitserver.FitServer fitServer = new fitnesse.fitserver.FitServer();
      fitServer.Run(CommandLineArguments);
      return fitServer.ExitCode();
    }
  }
}