Trax.Core.Testing
1.7.6
Prefix Reserved
See the version list below for details.
dotnet add package Trax.Core.Testing --version 1.7.6
NuGet\Install-Package Trax.Core.Testing -Version 1.7.6
<PackageReference Include="Trax.Core.Testing" Version="1.7.6" />
<PackageVersion Include="Trax.Core.Testing" Version="1.7.6" />
<PackageReference Include="Trax.Core.Testing" />
paket add Trax.Core.Testing --version 1.7.6
#r "nuget: Trax.Core.Testing, 1.7.6"
#:package Trax.Core.Testing@1.7.6
#addin nuget:?package=Trax.Core.Testing&version=1.7.6
#tool nuget:?package=Trax.Core.Testing&version=1.7.6
Trax.Core
Railway Oriented Programming for .NET. Build trains that carry data through a sequence of stops, with automatic derailment handling when something goes wrong.
A train (Train<TIn, TOut>) declares a chain of junctions (Junction<TIn, TOut>), small classes that each do one thing. The train keeps every value it has seen in a type-keyed memory, hands each junction the input and constructor arguments it asks for, and stops at the first junction that throws, returning Either<Exception, TOut> instead of throwing. Trax.Core is the in-process foundation of the Trax packages; the layers above it add dependency injection, execution logging, dispatch, scheduling and a dashboard.
dotnet add package Trax.Core
dotnet add package Trax.Core.Testing # optional: architecture-guard test fixtures
Documentation: traxsharp.net/docs.
The Trax Stack
Trax is a layered framework split across several repos. You can stop at whatever layer solves your problem. You are here: Trax.Core.
| Repo | Adds |
|---|---|
| Trax.Core | Pipelines, junctions, railway error propagation |
| Trax.Effect | Execution logging, DI, pluggable storage |
| Trax.Mediator | Decoupled dispatch via TrainBus |
| Trax.Scheduler | Cron schedules, retries, dead-letter queues |
| Trax.Api | GraphQL API for remote access |
| Trax.Dashboard | Blazor monitoring UI |
| Trax.Cli | trax-cli project scaffolding tool |
| Trax.Samples | Sample apps and a dotnet new template |
Full documentation: traxsharp.net/docs.
Why?
Error handling tends to bury the actual logic:
public async Task<OrderReceipt> ProcessOrder(OrderRequest request)
{
var inventory = await _inventory.CheckAsync(request.Items);
if (!inventory.Available)
return Error("Items out of stock");
var payment = await _payments.ChargeAsync(request.PaymentMethod, request.Total);
if (!payment.Success)
return Error("Payment failed");
var shipment = await _shipping.CreateAsync(request.Address, request.Items);
if (shipment == null)
return Error("Shipping setup failed");
return new OrderReceipt(payment, shipment);
}
Every junction needs its own null check, error branch, and early return. The business logic (check inventory, charge payment, create shipment) gets lost in the noise.
With Trax.Core
public class ProcessOrderTrain : Train<OrderRequest, OrderReceipt>
{
protected override Task<Either<Exception, OrderReceipt>> Junctions() =>
Chain<CheckInventoryJunction>()
.Chain<ChargePaymentJunction>()
.Chain<CreateShipmentJunction>()
.Resolve();
}
A train picks up its cargo, visits each stop along the route (.Chain<T>), and arrives at its destination (Resolve). If CheckInventoryJunction throws, the train derails and ChargePaymentJunction and CreateShipmentJunction are never reached. The exception propagates through the chain automatically.
Main Track: Input → [Stop 1] → [Stop 2] → [Stop 3] → Output
↓
Derailed: Exception → [Skip] → [Skip] → Exception
Each junction is its own class with its own dependencies, testable in isolation.
Installation
Requires net10.0.
dotnet add package Trax.Core
Trax.Core.Analyzers is deprecated and reports nothing; do not install it.
Quick Start
1. Define junctions. Each junction takes one type of cargo in and produces one type of cargo out. Its constructor arguments are taken from the train's memory:
using LanguageExt;
using Trax.Core.Junction;
using Trax.Core.Train;
public record CreateUserRequest(string Email);
public record User(Guid Id, string Email);
public interface IUserRepository
{
Task<User?> GetByEmailAsync(string email);
Task<User> AddAsync(string email);
}
public class ValidateEmailJunction(IUserRepository repo) : Junction<CreateUserRequest, Unit>
{
public override async Task<Unit> Run(CreateUserRequest input)
{
if (await repo.GetByEmailAsync(input.Email) is not null)
throw new InvalidOperationException($"Email {input.Email} is already taken");
return Unit.Default;
}
}
public class CreateUserInDatabaseJunction(IUserRepository repo) : Junction<CreateUserRequest, User>
{
public override Task<User> Run(CreateUserRequest input) => repo.AddAsync(input.Email);
}
2. Build a route by chaining junctions into a train:
public class CreateUserTrain(IUserRepository repo) : Train<CreateUserRequest, User>
{
protected override Task<Either<Exception, User>> Junctions() =>
AddServices(repo)
.Chain<ValidateEmailJunction>()
.Chain<CreateUserInDatabaseJunction>()
.Resolve();
}
When the train is run with an input, the cargo is loaded automatically. AddServices puts the repository on board, so each junction's constructor can take it. At each stop, .Chain<T> picks up the cargo T needs from what the train is carrying, runs the junction, and loads the output back on. Resolve unloads the final delivery at the destination.
The train carries all of this in Memory, a type-keyed store that accumulates as the train moves through its route. Each stop can use anything a previous stop produced.
With Trax.Effect, a ServiceTrain resolves junction dependencies from the DI container instead, so AddServices is not needed.
3. Run it:
// repo is any IUserRepository implementation
var train = new CreateUserTrain(repo);
Either<Exception, User> result = await train.RunEither(new CreateUserRequest("ada@example.com"));
// Or throw on failure:
User user = await new CreateUserTrain(repo).Run(new CreateUserRequest("grace@example.com"));
Startup Chain Verification
A train's chain is a declaration, so a host can check it before serving traffic. With the mediator registered, every train's Junctions() is read at startup and replayed over the types Memory would hold; if a junction expects cargo no earlier stop loads, or the chain ends without the train's result, the host refuses to start and names every train that cannot run.
The Roslyn analyzer this package used to describe here (CHAIN001, CHAIN002) is deprecated: it only reads chains rooted at Activate(), which can no longer be written.
IDE Extensions
Inlay hint extensions show TIn → TOut types inline for each .Chain<TJunction>() call, so you can see what cargo flows through each stop at a glance.
- VSCode: Trax.Core Chain Hints on the Marketplace
- Rider / ReSharper: Search for Trax.Core Chain Hints in JetBrains Marketplace
Next Layer
When you need execution logging, DI, or persistent metadata, move up to Trax.Effect.
License
MIT
Trademark & Brand Notice
Trax is an open-source .NET framework provided by TraxSharp. This project is an independent community effort and is not affiliated with, sponsored by, or endorsed by the Utah Transit Authority, Trax Retail, or any other entity using the "Trax" name in other industries.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- Microsoft.CodeAnalysis.CSharp (>= 5.9.0)
- NUnit (>= 4.4.0)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on Trax.Core.Testing:
| Package | Downloads |
|---|---|
|
Trax.Effect.Data.Testing
Architecture-guard checkers for the Trax data layer (DomainDataContext, one-schema-per-context, cross-schema reads). Framework-agnostic: returns offender lists you assert on. |
|
|
Trax.Api.GraphQL.Testing
Architecture-guard checkers for Trax GraphQL: cross-schema edge manifest validity and edge-resolver placement / loader use. Framework-agnostic: returns offender lists you assert on. |
|
|
Trax.Mediator.Testing
Architecture-guard checkers for Trax trains: every ServiceTrain has its companion I{Name} interface. Framework-agnostic: returns offender lists you assert on. |
GitHub repositories
This package is not used by any popular GitHub repositories.