xchain 0.3.2
See the version list below for details.
dotnet add package xchain --version 0.3.2
NuGet\Install-Package xchain -Version 0.3.2
<PackageReference Include="xchain" Version="0.3.2" />
<PackageVersion Include="xchain" Version="0.3.2" />
<PackageReference Include="xchain" />
paket add xchain --version 0.3.2
#r "nuget: xchain, 0.3.2"
#:package xchain@0.3.2
#addin nuget:?package=xchain&version=0.3.2
#tool nuget:?package=xchain&version=0.3.2
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
Padto 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 exceptionTExceptionwas 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
Flowdefinition 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.
- 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 | 672 | 6/29/2025 |
| 0.3.5-try-output.7 | 156 | 6/18/2025 |
| 0.3.5-try-output.2 | 160 | 6/18/2025 |
| 0.3.5-main.2 | 105 | 6/29/2025 |
| 0.3.5-main.1 | 158 | 6/18/2025 |
| 0.3.5-collection-link.18 | 108 | 6/29/2025 |
| 0.3.5-collection-link.17 | 117 | 6/29/2025 |
| 0.3.5-collection-link.12 | 334 | 6/19/2025 |
| 0.3.5-collection-link.6 | 168 | 6/18/2025 |
| 0.3.5-collection-link.5 | 169 | 6/18/2025 |
| 0.3.4 | 214 | 6/18/2025 |
| 0.3.4-test-output.3 | 153 | 6/18/2025 |
| 0.3.4-test-output.2 | 161 | 6/18/2025 |
| 0.3.4-test-output.1 | 155 | 6/18/2025 |
| 0.3.4-main.1 | 154 | 6/18/2025 |
| 0.3.3 | 292 | 6/16/2025 |
| 0.3.3-main.1 | 152 | 6/16/2025 |
| 0.3.3-chain-linker.7 | 167 | 6/16/2025 |
| 0.3.2 | 292 | 6/13/2025 |
| 0.3.2-main.2 | 224 | 6/13/2025 |