xchain 0.3.4-test-output.2
See the version list below for details.
dotnet add package xchain --version 0.3.4-test-output.2
NuGet\Install-Package xchain -Version 0.3.4-test-output.2
<PackageReference Include="xchain" Version="0.3.4-test-output.2" />
<PackageVersion Include="xchain" Version="0.3.4-test-output.2" />
<PackageReference Include="xchain" />
paket add xchain --version 0.3.4-test-output.2
#r "nuget: xchain, 0.3.4-test-output.2"
#:package xchain@0.3.4-test-output.2
#addin nuget:?package=xchain&version=0.3.4-test-output.2&prerelease
#tool nuget:?package=xchain&version=0.3.4-test-output.2&prerelease
Xchain
Xchain extends xUnit with a fluent mechanism to chain tests, pass data, and skip dependent tests if previous ones fail — ideal for integration or system tests with interdependencies.
Features
- Chained execution: Tests can conditionally run based on previous outcomes.
- Shared output state: Tests exchange data via
TestChainFixture
. - Skips on failure: Later tests are skipped if earlier ones fail.
- Custom ordering: Tests are run in a defined sequence using
Link
. - Named tests: Set display name with
Name
, auto-prepended with# Link
. - Zero-padded sorting: Use
Pad
to ensure consistent numeric display alignment. - Custom metadata: Add test traits via simple attribute classes.
Example: Chained Execution with Display Names
[TestCaseOrderer("Xchain.ChainOrderer", "Xchain")]
public class ChainTest(TestChainFixture chain) : IClassFixture<TestChainFixture>
{
[ChainFact(Link = 3, Name = "Throw Exception")]
public void Test1() => chain.LinkUnless<Exception>((output) =>
{
throw new NotImplementedException();
});
[ChainFact(Link = 2, Name = "Sleep 2 seconds")]
public async Task Test2() => await chain.LinkUnlessAsync<NotImplementedException>(async (output, cancellationToken) =>
{
var sleep = output.Get<int>("Sleep");
await Task.Delay(sleep, cancellationToken);
});
[ChainFact(Link = 1, Name = "Sleep 1 second", Pad = 2)]
[ChainTag(Owner = "Kethoneinuo", Category = "Important", Color = "Black")]
public async Task Test3() => await chain.LinkAsync(async (output, cancellationToken) =>
{
const int sleep = 1000;
output["Sleep"] = sleep * 2;
await Task.Delay(sleep, cancellationToken);
}, TimeSpan.FromMilliseconds(100));
[ChainFact(Link = 4, Name = "Fails again")]
public void Test4() => chain.LinkUnless<Exception>((output) =>
{
throw new NotImplementedException();
});
[ChainFact(Link = 5, Name = "Yet another fail")]
public void Test5() => chain.LinkUnless<Exception>((output) =>
{
throw new NotImplementedException();
});
}
Each test is displayed as:
#01 | Sleep 1 second
#2 | Sleep 2 seconds
#3 | Throw Exception
#4 | Fails again
#5 | Yet another fail
If Pad = 2
, it ensures alignment even when Link goes beyond 9 (e.g., #01
, #10
, #15
).
Fluent Chaining Methods
Link
— executes and captures exceptionsLinkUnless<TException>
— skips test if exceptionTException
was previously thrownLinkAsync
/LinkUnlessAsync<TException>
— async equivalents
Sharing Data Across Tests
Xchain uses a TestChainFixture
to share both output values and captured exceptions.
[TestCaseOrderer("Xchain.ChainOrderer", "Xchain")]
public class ChainTest(TestChainFixture chain) : IClassFixture<TestChainFixture>
{
[ChainFact(Link = 1, Name = "Setup")]
public void Test1() => chain.Output["Sleep"] = 1500;
[ChainFact(Link = 2, Name = "Sleep using shared value")]
public void Test2()
{
var sleep = (int)chain.Output["Sleep"];
Thread.Sleep(sleep);
}
}
Skipping on Previous Failures
[ChainFact(Link = 3, Name = "Failing Root")]
public void Root() => throw new TimeoutException();
[ChainFact(Link = 4, Name = "Skip if Exception")]
public void Dependent() => chain.LinkUnless<Exception>((output) =>
{
// This test will be skipped
});
Traits with Custom Attributes
You can define custom metadata for filtering and categorization.
[TraitDiscoverer("Xchain.TraitDiscoverer", "Xchain")]
[AttributeUsage(AttributeTargets.Method)]
public class ChainTagAttribute(string? owner = null, string? category = null, string? color = null)
: Attribute, ITraitAttribute
{
public string? Owner { get; set; } = owner;
public string? Category { get; set; } = category;
public string? Color { get; set; } = color;
}
Usage:
[ChainFact(Link = 1, Name = "Custom Tagged")]
[ChainTag(Owner = "Dev", Category = "Regression", Color = "Red")]
public void TaggedTest() => ...
Test Output Preview
Xchain.Tests.ChainTest: #1 | Sleep 1 second ✅ Passed
Xchain.Tests.ChainTest: #2 | Sleep 2 seconds ✅ Passed
Xchain.Tests.ChainTest: #3 | Throw Exception ❌ Failed
Xchain.Tests.ChainTest: #4 | Fails again ⚠️ Skipped due to prior failure
Xchain.Tests.ChainTest: #5 | Yet another fail ⚠️ Skipped due to prior failure
Summary
Feature | Description |
---|---|
ChainFact(Link) |
Defines order and enables chaining |
Name |
Sets test display name (with #Link ) |
Pad |
Pads link number (e.g., #01 ) |
LinkUnless<T> |
Skips if specific exception occurred |
Output[...] |
Share data between tests |
ChainTagAttribute |
Adds test traits dynamically |
Use Custom Attribute to set Flow for test collection
The ChainFactAttribute
supports a Flow
property, allowing test cases to be grouped under a common flow name. While ChainFactAttribute
is part of the library, you can define your own custom attributes by inheriting from it.
One example is FlowFactAttribute
, which sets a default flow name for all test cases in a test class. This avoids repeating the Flow = "..."
assignment in each test case.
Purpose
- Demonstrates how to inherit from
ChainFactAttribute
. - Centralizes the
Flow
definition in a single place. - Reduces redundancy in test annotations.
Example
// User-defined attribute
class FlowFactAttribute : ChainFactAttribute { public FlowFactAttribute() => Flow = "MyFlow"; }
Usage in tests:
[FlowFact(Link = 10, Name = "Sleep 1 second")]
public async Task Test1() => await chain.LinkAsync(...);
[FlowFact(Link = 20, Name = "Sleep 2 seconds")]
public async Task Test2() => await chain.LinkUnlessAsync<Exception>(...);
This is equivalent to using:
[ChainFact(Flow = "MyFlow", Link = 10, Name = "Sleep 1 second")]
but without repeating the Flow
parameter in every test case.
Collection-Level Ordering
Xchain supports chaining and data sharing across test collections using ChainLinkAttribute
and CollectionChainFixture
.
ChainLink Attribute & ChainLinker
Use [ChainLink(int)]
to assign a sequence to your test collections. Collections are ordered at runtime using the ChainLinker
test collection orderer.
Collection Fixture: CollectionChainFixture
Unlike TestChainFixture
, which is scoped per class, CollectionChainFixture
allows shared output across multiple test classes and collections. This enables interdependent suites to access a common state.
⚠️ Output keys must be unique across collections.
Example Setup
1. Define Collections with ChainLink
[CollectionDefinition("ChainTest")]
[ChainLink(1)]
public class ChainCollection : ICollectionFixture<CollectionChainFixture> { }
[CollectionDefinition("LinkedTest")]
[ChainLink(2)]
public class LinkedCollection : ICollectionFixture<CollectionChainFixture> { }
[CollectionDefinition("LastTest")]
[ChainLink(3)]
public class LastCollection : ICollectionFixture<CollectionChainFixture> { }
2. Configure Assembly for Ordered Execution
[assembly: TestCollectionOrderer("Xchain.ChainLinker", "Xchain")]
[assembly: CollectionBehavior(DisableTestParallelization = true, MaxParallelThreads = 0)]
You must disable parallelization to ensure predictable ordering across collections. You can use parallelization if required.
3. Use CollectionChainFixture
in Your Tests
[Collection("ChainTest")]
[TestCaseOrderer("Xchain.ChainOrderer", "Xchain")]
public class ChainTest(CollectionChainFixture chain)
{
[ChainFact(Link = 1, Name = "Sleep 1 second")]
public async Task Test1() => await chain.LinkAsync(async (output, token) =>
{
const int sleep = 1000;
output["Sleep"] = sleep * 2;
await Task.Delay(sleep, token);
});
[ChainFact(Link = 2, Name = "Sleep 2 seconds")]
public async Task Test2() => await chain.LinkUnlessAsync<NotImplementedException>(async (output, token) =>
{
var sleep = output.Get<int>("Sleep");
await Task.Delay(sleep, token);
});
[ChainFact(Link = 3, Name = "Fail")]
public void Test3() => chain.Link(() =>
{
throw new NotImplementedException();
});
}
[Collection("LinkedTest")]
public class LinkedTest(CollectionChainFixture chain)
{
[Fact]
public void TestA() => chain.Link(output =>
{
Thread.Sleep(1000);
});
[Fact]
public void TestB() => chain.Link(output =>
{
Thread.Sleep(1000);
throw new NotImplementedException();
});
}
[Collection("LastTest")]
public class LastTest(CollectionChainFixture chain)
{
[Fact]
public void FinalTest() => chain.Link(output =>
{
var sleep = output.Get("Sleep");
Console.WriteLine($"Final sleep value: {sleep}");
Thread.Sleep(1000);
});
}
Summary of New Features
Feature | Description |
---|---|
ChainLinkAttribute |
Assigns execution order to test collections |
ChainLinker |
Orders collections based on ChainLink |
CollectionChainFixture |
Shares output across multiple test classes |
TestCollectionOrderer |
Assembly-level setup for cross-collection order |
DisableTestParallelization |
Ensures sequential collection execution |
Synchronizing Test Collections with ChainLinkFixture and ChainAwaiterFixture
Xchain supports runtime coordination between test collections using ChainLinkFixture
and ChainAwaiterFixture
. This allows a test collection to start only after another collection has completed, even when collections are allowed to run in parallel globally.
Purpose
- Coordinate execution between specific test collections without disabling global parallelization.
- Prevent race conditions or conflicts when collections depend on shared state or resources.
- Maintain logical execution order beyond static collection ordering.
Usage Overview
ChainAwaiter
is an internal component.- Users interact only with:
ChainLinkFixture
to register a collection under a name/key.ChainAwaiterFixture
to wait for a registered collection to finish.
Example: Coordinated Collections
// Registers the collection as "WaitForMe"
public class LongRunningCollectionFixture()
: ChainLinkFixture("WaitForMe");
// Waits until "WaitForMe" is completed before starting
public class WaitForLongRunningCollectionFixture()
: ChainAwaiterFixture("WaitForMe");
Define your collections:
[CollectionDefinition("First")]
public class FirstCollection : ICollectionFixture<CollectionChainFixture> { };
// Waits for "WaitForMe" before executing
[CollectionDefinition("Second")]
public class SecondCollection
: ICollectionFixture<WaitForLongRunningCollectionFixture>,
ICollectionFixture<CollectionChainFixture> { };
// Registers itself as "WaitForMe"
[CollectionDefinition("Third")]
public class LinkedCollection
: ICollectionFixture<LongRunningCollectionFixture>,
ICollectionFixture<CollectionChainFixture> { }
// Also waits for "WaitForMe"
[CollectionDefinition("Four")]
public class LastCollection
: ICollectionFixture<WaitForLongRunningCollectionFixture>,
ICollectionFixture<CollectionChainFixture> { };
In this scenario:
LinkedCollection
(Third) begins first and registers itself as"WaitForMe"
usingChainLinkFixture
.SecondCollection
andLastCollection
useChainAwaiterFixture("WaitForMe")
to delay execution until the registration is marked as complete.
Optional Timeout
You can specify a timeout for how long a collection should wait:
public class WaitWithTimeoutFixture()
: ChainAwaiterFixture("WaitForMe", TimeSpan.FromMinutes(2)) { }
Summary
Component | Purpose |
---|---|
ChainLinkFixture(name) |
Registers the current collection under a given key |
ChainAwaiterFixture(name) |
Waits until the named collection has completed |
ChainAwaiter (internal) |
Manages registration and synchronization internally |
This mechanism allows partial parallelization with explicit coordination where needed.
- Powered by Xunit.SkippableFact
- Created from JandaBox
- Box icon by Freepik – Flaticon
Product | Versions 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. |
-
.NETStandard 2.1
- xunit (>= 2.9.3)
- xunit.abstractions (>= 2.0.3)
- xunit.extensibility.core (>= 2.9.3)
- Xunit.SkippableFact (>= 1.5.23)
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 | 449 | 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 | 80 | 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 | 125 | 5/18/2025 |
0.1.0-chain-orderer.3 | 123 | 5/18/2025 |