Skip to main content

Compartments

.NET only

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