Table of Contents

Getting Started

Installation

Consume this library via its NuGet Package. Click on the badge to find its latest version and the instructions for consuming it that best apply to your project.

NuGet package

Default Fact behavior

/// <summary>
/// Demonstrates that xunit [Fact] behavior is to invoke the test on an MTA thread.
/// </summary>
[Fact]
public async Task Fact_OnMTAThread()
{
    ApartmentState expectedApartment = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
        ? ApartmentState.MTA
        : ApartmentState.Unknown;
    Assert.Equal(expectedApartment, Thread.CurrentThread.GetApartmentState());
    await Task.Yield();
    Assert.Equal(expectedApartment, Thread.CurrentThread.GetApartmentState());
}

Usage

Portable UI

Best when you need basic UI thread semantics for tests that may run on any OS. You'll get an STA thread on Windows.

/// <summary>
/// Demonstrates that the <see cref="UIFactAttribute"/> can run on all platforms,
/// and emulates a UI thread (not specific to WPF or WinForms) with a <see cref="SynchronizationContext"/>
/// that keeps yielding awaits on the original thread.
/// </summary>
[UIFact]
public async Task UIFact_OnSTAThread()
{
    int initialThread = Environment.CurrentManagedThreadId;
    Assert.NotNull(SynchronizationContext.Current);
    ApartmentState expectedApartment = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
        ? ApartmentState.STA
        : ApartmentState.Unknown;
    Assert.Equal(expectedApartment, Thread.CurrentThread.GetApartmentState());

    await Task.Yield();
    Assert.Equal(initialThread, Environment.CurrentManagedThreadId);
    Assert.NotNull(SynchronizationContext.Current);
}

WPF

More closely resembles WPF-specific semantics including a WPF-specific SynchronizationContext.

/// <summary>
/// Demonstrates that <see cref="WpfFactAttribute"/> invokes tests expecting an STA thread
/// specifically with a WPF SynchronizationContext.
/// </summary>
[WpfFact]
public async Task WpfFact_OnSTAThread()
{
    Assert.Equal(ApartmentState.STA, Thread.CurrentThread.GetApartmentState());
    Assert.IsType<DispatcherSynchronizationContext>(SynchronizationContext.Current);

    await Task.Yield();

    Assert.Equal(ApartmentState.STA, Thread.CurrentThread.GetApartmentState()); // still there
    Assert.IsType<DispatcherSynchronizationContext>(SynchronizationContext.Current);
}

WinForms

More closely resembles WinForms-specific semantics including a WinForms-specific SynchronizationContext.

/// <summary>
/// Demonstrates that <see cref="WinFormsFactAttribute"/> invokes tests expecting an STA thread
/// specifically with a WinForms SynchronizationContext.
/// </summary>
[WinFormsFact]
public async Task WinFormsFact_OnSTAThread()
{
    Assert.Equal(ApartmentState.STA, Thread.CurrentThread.GetApartmentState());
    Assert.IsType<WindowsFormsSynchronizationContext>(SynchronizationContext.Current);

    await Task.Yield();

    Assert.Equal(ApartmentState.STA, Thread.CurrentThread.GetApartmentState()); // still there
    Assert.IsType<WindowsFormsSynchronizationContext>(SynchronizationContext.Current);
}

STA thread

Guarantees the test to run on an STA thread. Applicable only on Windows. Because no SynchronizationContext is applied by default, an async test will resume on a threadpool thread instead of the test thread after a yielding await.

/// <summary>
/// Demonstrates that <see cref="StaFactAttribute"/> invokes tests expecting an STA thread
/// but with no <see cref="SynchronizationContext"/> at all, so any yielding await will
/// return on the threadpool (an MTA thread).
/// </summary>
[StaFact]
public async Task StaWithoutSyncContext()
{
    Assert.Equal(ApartmentState.STA, Thread.CurrentThread.GetApartmentState());
    Assert.Null(SynchronizationContext.Current);

    await Task.Yield();

    // Without a single-threaded SynchronizationContext, we won't come back to the STA thread.
    Assert.Equal(ApartmentState.MTA, Thread.CurrentThread.GetApartmentState());
    Assert.Null(SynchronizationContext.Current);
}