Instantiating a WPF control from an NUnit test

Posted by Chris on January 08, 2007

If you try to run a test like the following from NUnit, you will find that it does not work. You get an InvalidOperationException telling you that “The calling thread must be STA, because many UI components require this”.

[Test]
public void CanCreateAndShowWpfWindow()
{
  Console.WriteLine(Thread.CurrentThread.GetApartmentState());

  System.Windows.Window window = new System.Windows.Window();
  window.Show();
}

NUnit (both the GUI and console version) by default runs it’s threads in a multithreaded apartment (MTA), which can be verified by looking at the Console.Out tab. As the message says, instantiating a WPF Window requires that the calling thread is running in a single-threaded apartment (STA).

The simplest way to make this work is to configure NUnit to run tests in an STA thread. Create a config file for NUnit to use (see the docs for info on how to name it) and add the following XML configuration (or just add to your existing one):


<configuration>
  <configSections>
    <sectionGroup name="NUnit">
      <section type="System.Configuration.NameValueSectionHandler"
               name="TestRunner"></section>
    </sectionGroup>
  </configSections>

  <NUnit>
    <TestRunner>
      <add value="STA" key="ApartmentState"></add>
    </TestRunner>
  </NUnit>
</configuration>

Config updated with changes proposed in comments by JohnD. Thanks!

This tells NUnit to run all tests in STA threads. However, if you just want a single test (or some but not all) to run in STA (or MTA, if you have used the above configuration to set the default to STA) you have to resort to code. You need to start a new thread and set the apartment state you want, and then run your unit test in that thread. I used CrossThreadTestRunner (from Peter Provost) and just added support to choose which apartment state to create and run a thread in. The following code example shows how to use it:

[Test]
public void CanCreateAndShowWpfWindow()
{
  CrossThreadTestRunner runner = new CrossThreadTestRunner();
  runner.RunInSTA(
    delegate
    {
      Console.WriteLine(Thread.CurrentThread.GetApartmentState());

      System.Windows.Window window = new System.Windows.Window();
      window.Show();
    });
}

My modified CrossThreadTestRunner class looks like this:

using System;
using System.Reflection;
using System.Security.Permissions;
using System.Threading;

namespace UnitTestThreadApartmentState
{
  public class CrossThreadTestRunner
  {
    private Exception lastException;

    public void RunInMTA(ThreadStart userDelegate)
    {
      Run(userDelegate, ApartmentState.MTA);
    }

    public void RunInSTA(ThreadStart userDelegate)
    {
      Run(userDelegate, ApartmentState.STA);
    }

    private void Run(ThreadStart userDelegate, ApartmentState apartmentState)
    {
      lastException = null;

      Thread thread = new Thread(
        delegate()
        {
          try
          {
            userDelegate.Invoke();
          }
          catch (Exception e)
          {
            lastException = e;
          }
        });
      thread.SetApartmentState(apartmentState);

      thread.Start();
      thread.Join();

      if (ExceptionWasThrown())
        ThrowExceptionPreservingStack(lastException);
    }

    private bool ExceptionWasThrown()
    {
      return lastException != null;
    }

    [ReflectionPermission(SecurityAction.Demand)]
    private static void ThrowExceptionPreservingStack(Exception exception)
    {
      FieldInfo remoteStackTraceString = typeof(Exception).GetField(
        "_remoteStackTraceString",
        BindingFlags.Instance | BindingFlags.NonPublic);
      remoteStackTraceString.SetValue(exception, exception.StackTrace + Environment.NewLine);
      throw exception;
    }
  }
}

A final note: As opposed to NUnit, both TestDriven.Net and Resharper’s Unit Test Runner run tests in STA threads. I got bitten by this recently when all the tests where green on my machine, but when I committed the build server went red on me. I was using Resharper to run the tests, while NUnit-Console was used on the server. Hope this might help someone else in the same situation.

Trackbacks

Use this link to trackback from your own site.