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.

  • Chris,

    excellent post; it seems with R#5 JetBrains has flipped this, so the runner runs in MTA.
  • Your CrossThreadTestRunner class worked perfectly! Thank you!
  • Eric
    Works like a charm! Thanks!
  • Sunit Joshi
    Thanks Chris...this helped me too. My failed tests were driving me nuts earlier !!
  • Ben Rice
    I've had no luck reading a config file in my test project.

    For my dll ProjectTests.dll I created a file ProjectTests.config with the configuration you have above.

    I'm testing:
    Assert.AreEqual("STA", ConfigurationManager.AppSettings["ApartmentState"]); // fails, is null

    In fact, Configuration.AppSettings has no keys at all. Any ideas?
  • Chris
    Unfortunately, the plugin I was using for showing code nicely made all xml-tags lowercase, which broke the config file I include in the post. I have now edited it, so it looks like JohnD recommends above (and how it would have looked if not for that plugin...), so now it should work better.
  • Hey Chris,
    You have a bunch of UPPERCASE stuff in those samples that breaks them. Any chance of editing them to work correctly?
  • Chris
    Hi Drew,

    I am not quite sure what you mean by configuring Resharper? You could of course create a template to avoid having to write the boiler-plate code manually each time, but maybe you are referring to something else?
  • Drew
    Hi Chris,

    I was wondering if you are aware of a way of configuring resharper to use these settings?

    Cheers,
    Drew
  • Yingqiang Woo
    hi,JohnD
    could you send me the config file, wuyq101 @ gmail.com.
    Thank you very much.
    i am using Nunit 2.4.3, i just can't get it work.
  • Chris
    John, thanks for sharing that information. Hope it helps others in the same situation.
  • JohnD
    Hey!

    The XML I put in the last post didn't show up. So, I'll describe what I had to do:
    1. Create a config file for the test project or test DLL. Making the changes in NUnit's main config file didn't get the job done.
    2. NUnit is case sensitive so the XML names must be NUnit and TestRunner rather than nunit and testrunner.
    3. The setting of NUnit/TestRunner must be within the configuration tag rather than outside it as shown in the original example.

    When set up like that, Nunit 2.4.6 runs WPF tests just fine!
    Good luck!
    JohnD
  • JohnD
    The following test project config file gets the job done for NUnit 2.4.6











  • JohnD
    Hey Chris,

    What version of NUnit are you using? I tried your technique to run all tests as STA with NUnit 2.4.6 and had no luck at all! Are we using different versions of NUnit or is there some magic setting that I failed to set appropriately? Here are the details behind my questions:

    When I set up the config file as you showed in the example, nunit-console complained that there were two root elements and pointed at the line.

    I moved the section inside the section and NUnit complained that sectiongroup was an unrecognized token. I removed the stuff and ended up with:

    ...




    ...



    ...


    At this point, nunit-console could successfully parse the config file but all the tests failed with the System.InvalidOperationException indicating that the config file changes were not having the desired result.

    Same sort of thing happened with the GUI test runner.

    Thanks for any help
    John
  • Chris, thanks for creating this article. I have recently created an NUnit Test Generator. What do you think would be useful features for interacting with WPF for the test generator?

    http://www.kellermansoftware.com/p-30-nunit-test-generator.aspx
blog comments powered by Disqus