Custom Adapter
Extend XceptoAdapter to create a user-facing domain test DSL. The adapter is registered once per test, has a lifecycle, and exposes methods that return fluent builders or register steps.
Minimal example
- .NET
- Java
public sealed class MyServiceAdapter : XceptoAdapter
{
private readonly MyServiceClient _client;
public MyServiceAdapter(MyServiceClient client)
{
_client = client;
}
// Simple atomic action — always transitions immediately
public void PerformAction(Action action)
{
AddStep(new LambdaState("Perform action",
onEnter: _ => action(),
condition: _ => true));
}
// Expectation — retried until matched
public void ExpectEvent(Predicate<MyEvent> predicate)
{
AddStep(new LambdaState("Expect event",
onEnter: _ => { },
condition: _ => _client.PollEvent(predicate)));
}
protected override Task Initialize(IServiceProvider sp) => Task.CompletedTask;
protected override Task Cleanup() => Task.CompletedTask;
}
public final class MyServiceAdapter extends XceptoAdapter {
private final MyServiceClient client;
public MyServiceAdapter(TransitionBuilder builder, MyServiceClient client) {
super(builder);
this.client = client;
}
// Simple atomic action — always transitions immediately
public void performAction(Runnable action) {
addStep(new XceptoState("Perform action") {
@Override
public void onEnter(ServiceProvider sp) {
action.run();
}
@Override
public boolean evaluateConditionsForTransition(ServiceProvider sp) {
return true;
}
});
}
// Expectation — retried until matched
public void expectEvent(Predicate<MyEvent> predicate) {
addStep(new XceptoState("Expect event") {
@Override
public void onEnter(ServiceProvider sp) {}
@Override
public boolean evaluateConditionsForTransition(ServiceProvider sp) {
return client.pollEvent(predicate);
}
});
}
@Override protected void initialize(ServiceProvider sp) { client.connect(); }
@Override protected void terminate() { client.disconnect(); }
@Override protected void addServices(ServiceCollection sc) {}
}
Lifecycle
- .NET
- Java
| Method | When it runs |
|---|---|
Initialize(IServiceProvider) | Before the state machine starts; after all adapters are registered |
Cleanup() | After the state machine finishes or fails; always runs |
| Method | When it runs |
|---|---|
initialize(ServiceProvider) | Before the state machine starts; after all adapters are registered |
terminate() | After the state machine finishes or fails; always runs |
addServices(ServiceCollection) | During registration; use to inject dependencies into the shared service provider |
Prefer returning builders
For configurable domain operations, return a builder identity instead of registering a step directly. This lets the test chain multiple fluent calls before the step is built:
- .NET
- Java
public sealed class DomainAdapter : XceptoAdapter
{
private readonly DomainClient _client;
public DomainAdapter(DomainClient client) { _client = client; }
// Returns a builder — test chains .WithName().ShouldHaveStatus(...)
public EntityFlowBuilder Entity(string entityId)
{
return new EntityFlowBuilder(Builder)
.WithEntityId(entityId)
.WithClient(_client);
}
}
public final class DomainAdapter extends XceptoAdapter {
private final DomainClient client;
public DomainAdapter(TransitionBuilder builder, DomainClient client) {
super(builder);
this.client = client;
}
// Returns a builder — test chains .withDisplayName().shouldHaveStatus(...)
public EntityFlowBuilder entity(String entityId) {
return new EntityFlowBuilder(getBuilder())
.withEntityId(entityId)
.withClient(client);
}
}
Adapter builder
Pair the adapter with a configuration builder that registers it:
- .NET
- Java
public sealed class DomainAdapterBuilder
: AbstractAdapterBuilder<DomainAdapterBuilder, DomainAdapter>
{
private DomainClient _client = new();
public DomainAdapterBuilder(IStateMachineBuilder builder) : base(builder) {}
public DomainAdapterBuilder WithClient(DomainClient client)
{
_client = client;
return this;
}
public override DomainAdapter Build()
{
var adapter = new DomainAdapter(_client);
StateMachineBuilder.RegisterAdapter(adapter);
return adapter;
}
}
// Extension method for ergonomic access
public static class TransitionBuilderExtensions
{
public static DomainAdapterBuilder DomainAdapterBuilder(
this TransitionBuilder builder)
=> new(builder);
}
public final class DomainAdapterBuilder {
private final TransitionBuilder builder;
private DomainClient client = new DomainClient();
public DomainAdapterBuilder(TransitionBuilder builder) {
this.builder = builder;
}
public DomainAdapterBuilder withClient(DomainClient client) {
this.client = client;
return this;
}
public DomainAdapter build() {
var adapter = new DomainAdapter(builder, client);
builder.registerAdapter(adapter);
return adapter;
}
}