Skip to main content
System test framework

Wait for causality,
not for time.

Declare expected behavior as a state machine. Each step progresses when its condition is met — not when a timer fires. Xcepto handles retries, timeouts, and cleanup so your tests stay declarative and environment-independent.

.NETJava

var scenario = new ShipmentScenario();
await XceptoTest.Given(scenario, builder =>
{
var rest = builder.RestAdapterBuilder()
.WithBaseUrl(scenario.ApiAddress)
.WithSerializer(new NewtonsoftSerializer())
.Build();

rest.Post("/shipment/accept")
.WithRequestBody(() => new AcceptShipmentRequest(50))
.WithResponseType<AcceptShipmentResponse>()
.AssertThatResponse(r => r.Amount, Is.EqualTo(50));

// Retried until the assertion passes — no sleep needed
rest.Get("/inventory/stock")
.WithResponseType<StockResponse>()
.AssertThatResponse(r => r.Replenished, Is.True);
});
State Machine

Declare conditions, not timing

Given-When-Then is a mindset, not just syntax. POST and PATCH execute exactly once — actions that change state. GET, PUT, and DELETE retry automatically until their assertions pass or the timeout expires. No Thread.Sleep, no polling loops — just causal transitions that match how distributed systems actually behave.

// POST executes once — assertion failure is immediate
rest.Post("/shipment/accept")
.WithRequestBody(() => new AcceptShipmentRequest(50))
.WithResponseType<AcceptShipmentResponse>()
.AssertThatResponse(r => r.Amount, Is.EqualTo(50));

// GET retries until condition is met or timeout expires
rest.Get("/inventory/stock")
.WithResponseType<StockResponse>()
.AssertThatResponse(r => r.Replenished, Is.True);
REST Adapter

Typed responses and bearer tokens

Full JSON API support with typed response deserialization, bearer token authentication, and Promise<T> for passing data across steps. All HTTP verbs, query arguments, and custom assertions are included out of the box.

View docs →
Promise<TokenResponse> token = rest.Post("/auth/login")
.WithRequestBody(() => new LoginRequest(username, password))
.WithResponseType<TokenResponse>()
.AssertThatResponse(r => r.AccessToken, Is.Not.Empty)
.PromiseResponse();

rest.Get("/api/profile")
.WithBearerTokenClient(() => token.Resolve().AccessToken)
.WithResponseType<ProfileResponse>()
.AssertThatResponse(r => r.Username, Is.EqualTo(username));
SSR Adapter

Cookie-aware session testing

Each adapter instance carries its own cookie jar — session cookies persist automatically across steps. Test full login → authenticated page flows without any manual session management or cookie forwarding.

View docs →
var ssr = builder.SsrAdapterBuilder()
.WithBaseUrl(scenario.GuiAddress).Build();

ssr.Post("/auth/register")
.WithFormContent(new RegisterRequest(username, password).ToForm())
.AssertSuccess();

ssr.Post("/auth/login")
.WithFormContent(new LoginRequest(username, password).ToForm())
.AssertSuccess();

// Session cookie carried automatically — no manual wiring
ssr.Get("/dashboard")
.AssertThatResponseContentString(Does.Contain(username));
Promise

Pass data between steps

Promise<T> bridges step registration time and execution time. Capture a typed response or HTML body from one step, then consume it lazily in the next — driving paths, extracting tokens, or feeding form values.

View docs →
// A token is embedded in the rendered HTML page
Promise<string> page = ssr.Post("/token/create")
.WithFormContent(new TokenCreateRequest("deploy-key").ToForm())
.AssertSuccess()
.PromiseResponse();

// REST call resolves the token lazily at execution time
rest.Post("/api/env/create")
.WithBearerTokenClient(() => ExtractToken(page.Resolve()))
.AssertSuccess();
Custom Adapters

Build a domain test DSL

Adapters are plain objects — wired explicitly like any production dependency. That keeps execution order visible, data flow explicit, and coordination driven by conditions. Extend XceptoAdapter and XceptoState to create fluent chains that read as behavior, not infrastructure.

View docs →
public sealed class OrderAdapter : XceptoAdapter
{
public OrderFlowBuilder Order(string orderId)
=> new OrderFlowBuilder(Builder).WithOrderId(orderId);
}

// In a test — reads as domain behavior
var orders = new OrderAdapterBuilder(builder).Build();

orders.Order("order-42")
.WithAmount(100)
.ShouldReachStatus(OrderStatus.Fulfilled);