Compartments
Compartments are a .NET (Xcepto.NET) feature.
Compartments provide in-memory isolation for multiple systems hosted in one test process. Each compartment owns its own service provider, optional entry point, dependencies, and explicitly exposed services. This enables testing multi-component distributed systems deterministically, without network overhead.
When to use compartments
Use compartments when a test needs multiple isolated components — workers, clients, shards, or hosted processes — that still need deterministic in-memory access from Xcepto states.
Without compartments, running multiple services in a single test process risks shared static state, shared DI containers, and non-deterministic initialization order.
Basic structure
// Two isolated compartments sharing one service via explicit exposure
Compartment.From(new ServiceCollection()
.AddSingleton<OrderService>()
.AddSingleton<PrivateDependency>() // not shared — stays inside compartment 1
)
.ExposeService<OrderService>() // make OrderService available to compartment 2
.Identify("orders")
.Build();
Compartment.From(new ServiceCollection()
.AddSingleton<ShippingService>()
.AddSingleton<PrivateDependency>() // independent instance from compartment 1
)
.DependsOn<OrderService>() // receives the shared instance from "orders"
.Identify("shipping")
.Build();
CompartmentalizedXceptoScenario
Use CompartmentalizedXceptoScenario as the scenario base class. It registers a CompartmentRepository, activates compartments, exposes selected services to the outer service provider, and starts compartment entry points as fire-and-forget tasks:
public class DistributedWarehouseScenario : CompartmentalizedXceptoScenario
{
protected override ScenarioSetup Setup(ScenarioSetupBuilder builder)
{
builder.AddCompartment(Compartment.From(new ServiceCollection()
.AddSingleton<InboundService>())
.Identify("inbound")
.WithEntryPoint<InboundService>(s => s.RunAsync)
.Build());
builder.AddCompartment(Compartment.From(new ServiceCollection()
.AddSingleton<StocktakeService>())
.DependsOn<InboundService>()
.Identify("stocktake")
.Build());
return builder.Build();
}
}
Accessing compartment services from states
Use CompartmentRepository to access services from within a state:
public sealed class StockLevelState : XceptoState
{
private readonly int _expected;
public StockLevelState(string name, int expected) : base(name)
{
_expected = expected;
}
public override Task OnEnter(IServiceProvider sp) => Task.CompletedTask;
public override async Task<bool> EvaluateConditionsForTransition(
IServiceProvider sp)
{
var repo = sp.GetRequiredService<CompartmentRepository>();
var stocktake = repo.GetService<StocktakeService>("stocktake");
var level = await stocktake.GetStockLevelAsync();
return level == _expected;
}
}
Design rules
- Give each compartment a stable, unique identity — used for lookup via
CompartmentRepository - Expose only services that other compartments or states legitimately need — keep internal dependencies private
- Use entry points for long-running simulated components that should start alongside the test
- Access compartment services from states, not from the test method directly