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):


  
    
      

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

Trackbacks are closed.

Comments

Leave a response

  1. Greg Finzer Fri, 28 Sep 2007 18:37:48 EDT

    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

  2. JohnD Thu, 24 Jan 2008 00:05:12 EST

    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

  3. JohnD Fri, 25 Jan 2008 00:43:02 EST

    The following test project config file gets the job done for NUnit 2.4.6

  4. JohnD Fri, 25 Jan 2008 00:54:34 EST

    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

  5. Chris Tue, 05 Feb 2008 16:44:15 EST

    John, thanks for sharing that information. Hope it helps others in the same situation.

  6. Yingqiang Woo Tue, 26 Feb 2008 10:19:56 EST

    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.

  7. Drew Fri, 29 Aug 2008 08:49:47 EDT

    Hi Chris,

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

    Cheers,
    Drew

  8. Chris Fri, 05 Sep 2008 08:30:33 EDT

    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?

  9. Richard Fri, 05 Sep 2008 16:16:36 EDT

    Hey Chris,
    You have a bunch of UPPERCASE stuff in those samples that breaks them. Any chance of editing them to work correctly?

Comments