xchain 0.4.0

dotnet add package xchain --version 0.4.0
                    
NuGet\Install-Package xchain -Version 0.4.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="xchain" Version="0.4.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="xchain" Version="0.4.0" />
                    
Directory.Packages.props
<PackageReference Include="xchain" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add xchain --version 0.4.0
                    
#r "nuget: xchain, 0.4.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package xchain@0.4.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=xchain&version=0.4.0
                    
Install as a Cake Addin
#tool nuget:?package=xchain&version=0.4.0
                    
Install as a Cake Tool

XChain

Build NuGet

XChain extends xUnit with structured test chaining, shared context between steps, and smart skipping based on previous failures. It's ideal for integration tests, API flows, or any scenario where test steps depend on each other and need to run in a defined order.

Features

  • Chain test steps using [ChainFact] or [ChainTheory] with readable display names.
  • Share structured state via TestChainOutput.
  • Automatically skip downstream tests when failures occur.
  • Chain multiple test collections, synchronizing execution with full isolation.
  • Filter test runs using custom traits with strongly-typed metadata.
  • Compatible with async workflows and supports rich diagnostics.

Why XChain?

Traditional xUnit tests are isolated by design — great for unit tests, but limiting for integration or scenario tests where later steps depend on earlier ones.

XChain solves this by enabling:

  • Logical chaining within a class (Link-ordered [ChainFact])
  • Shared, strongly-typed test output values
  • Explicit collection orchestration
  • Exception tracking and conditional skipping
  • Filtering with trait-based metadata

How It Works

XChain uses xUnit’s extensibility to provide:

  • Custom [ChainFact] / [ChainTheory] attributes that inject ordering, naming, and flow grouping.
  • A fixture-based pattern (TestChainContextFixture) for output sharing and exception flow.
  • Fluent APIs like .Link, .LinkUnless, .LinkWithCollection, etc. to capture and control flow.
  • Collection coordination using dedicated Setup and Await fixtures.
  • Strong typing for dictionary-style output sharing.

Quick Start

[TestCaseOrderer("Xchain.TestChainOrderer", "Xchain")]
public class SimpleTestChain(TestChainContextFixture chain) : IClassFixture<TestChainContextFixture>
{
    [ChainFact(Link = 1, Name = "Prepare Data")]
    public void Step1() =>
        chain.Link(output => output["Token"] = "abc123");

    [ChainFact(Link = 2, Name = "Use Data")]
    public void Step2() =>
        chain.Link(output =>
        {
            var token = output.Get<string>("Token");
            Assert.Equal("abc123", token);
        });
}

Each [ChainFact] defines a link in the chain. You can pass values between steps using output, and if a previous step fails, later steps can be automatically skipped.

Key Concepts

XChain builds upon xUnit's extensibility to introduce a minimal and powerful abstraction for test chaining. Here are the key features and how they work:

ChainFact and ChainTheory

These are enhanced versions of [Fact] and [Theory] that introduce:

  • Link-based ordering: Test execution is driven by an integer Link, which determines order within the test class.
  • Flow grouping: An optional Flow label allows grouping and visualizing test steps in Test Explorer.
  • Named steps: The Name property contributes to human-readable, sortable test display names.
[ChainFact(Link = 10, Name = "Login", Flow = "User Flow")]
public void LoginTest() => ...

The display name will appear as:

#10 | User Flow | Login

This improves traceability in test logs and IDE explorers.

XChain provides fluent methods to structure test execution:

  • Link: Run test logic and track exceptions.
  • LinkUnless<TException>: Skip test if a specific exception already occurred.
  • LinkAsync: Asynchronous variant with timeout support.
  • SkipIf<TException>: Explicitly skip a test if a condition exists in error history.

Each method records failures into a structured error stack (TestChainErrors), and subsequent steps can respond accordingly.

Shared Context (TestChainOutput)

A shared, thread-safe dictionary allows test steps to share values:

chain.Link(output => output["UserId"] = 123);
...
chain.Link(output =>
{
    var userId = output.Get<int>("UserId");
    ...
});

You can also build reusable strongly typed accessors using TestOutput<TCollection, TOutput> to ensure key uniqueness across collections.

Chaining Collections

XChain enables one collection to wait for another to finish using:

  • CollectionChainLinkSetupFixture<T> — registers a collection as a chain step.
  • CollectionChainLinkAwaitFixture<T> — blocks until the registered collection completes.

This is essential for cross-collection orchestration:

public class SetupFixture : CollectionChainLinkSetupFixture<MyCollection>;
public class AwaitFixture : CollectionChainLinkAwaitFixture<MyCollection>;

You can then fluently access shared output using:

chain.LinkWithCollection<PreviousCollection>(..., ...)

Note: Collection timeouts default to 6 minutes. This can be configured in the CollectionChainLinkAwaitFixture.

Global Metadata via Traits

XChain includes a TraitDiscoverer that turns strongly-typed attributes into metadata for filtering and organizing tests.

[Metadata("SmokeTests")]
public class MyTestSuite { }

This can be used to group collections and run them by trait.

Optional Collection Orderer

You may also define collection-wide execution order using:

[CollectionDefinition("MyCollection")]
[CollectionChainOrder(1)]
public class OrderedCollection { }

However, this requires global test parallelism to be disabled, so it is generally less recommended than explicit Await/Setup chaining.


Each of these features builds on xUnit principles while maintaining full compatibility. You can opt-in incrementally — use as little or as much as your test architecture requires.

Creating Strongly Typed Output Keys

When working with shared outputs across tests or collections, it's helpful to avoid using raw string keys. XChain supports strongly typed access via TestOutput<TCollection, TOutput>, which encapsulates the key naming and value casting.

Step 1: Define a Typed Extension

Create an extension method to encapsulate a reusable key. Here's an example for a shared Guid:

public static class TestChainOutputExtensions
{
    public static TestOutput<T, Guid> SharedId<T>(this TestChainOutput output) =>
        new(output, "SharedId");
}

The T type ensures key uniqueness by prefixing it with the type name (e.g., SetupTests_SharedId), making it safe across multiple collections.

Step 2: Store a Value in the Output

In the producing test or collection, use Put to store a value:

[Collection("SetupCollection")]
public class SetupTests(CollectionChainContextFixture chain)
{
    [ChainFact(Link = 1, Name = "Generate Id")]
    public void GenerateId() =>
        chain.Link(output => output.SharedId<SetupTests>().Put(Guid.NewGuid()));
}

Step 3: Retrieve the Value from Another Collection

In the consuming test, use Get to retrieve the value:

[Collection("ConsumerCollection")]
public class ConsumerTests(CollectionChainContextFixture chain)
{
    [ChainFact(Link = 1, Name = "Read Shared Id")]
    public void ReadId() =>
        chain.Link(output =>
        {
            var id = output.SharedId<SetupTests>().Get();
            Assert.NotEqual(Guid.Empty, id);
        });
}

Benefits

  • Type-safe access to output data.
  • Fluent syntax for both producers and consumers.
  • Unique scoping of keys per test or collection type.
  • Simplified refactoring, avoiding scattered string literals.

This pattern helps enforce clarity and consistency when passing values between chained tests.


Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.4.0 445 6/29/2025
0.3.5-try-output.7 120 6/18/2025
0.3.5-try-output.2 121 6/18/2025
0.3.5-main.2 74 6/29/2025
0.3.5-main.1 122 6/18/2025
0.3.5-collection-link.18 69 6/29/2025
0.3.5-collection-link.17 71 6/29/2025
0.3.5-collection-link.12 285 6/19/2025
0.3.5-collection-link.6 119 6/18/2025
0.3.5-collection-link.5 117 6/18/2025
0.3.4 146 6/18/2025
0.3.4-test-output.3 118 6/18/2025
0.3.4-test-output.2 119 6/18/2025
0.3.4-test-output.1 121 6/18/2025
0.3.4-main.1 118 6/18/2025
0.3.3 231 6/16/2025
0.3.3-main.1 121 6/16/2025
0.3.3-chain-linker.7 129 6/16/2025
0.3.2 230 6/13/2025
0.3.2-main.2 194 6/13/2025
0.3.2-flow-fact.3 238 6/13/2025
0.3.2-chain-linker.6 263 6/11/2025
0.3.1 308 5/26/2025
0.3.1-skipped-reason-emoji.1 128 5/26/2025
0.3.1-main.1 126 5/26/2025
0.3.0 153 5/26/2025
0.2.2-main.1 128 5/26/2025
0.2.2-chain-name-and-order.7 79 5/25/2025
0.2.2-chain-name-and-order.5 46 5/24/2025
0.2.1 158 5/22/2025
0.2.1-trait-discoverer.1 120 5/21/2025
0.2.1-main.1 122 5/22/2025
0.2.0 165 5/19/2025
0.1.0 162 5/18/2025
0.1.0-test-chain.3 127 5/18/2025
0.1.0-main.4 122 5/18/2025
0.1.0-main.2 128 5/18/2025
0.1.0-ci.1 124 5/18/2025
0.1.0-chain-orderer.3 123 5/18/2025