Common patterns in Accordant - response-dependent state, request derivations, error handling, and HTTP integration
Common Patterns in Accordant
This skill covers frequently-used patterns when building Accordant specs.
Response-Dependent State
When state depends on server-generated values (IDs, timestamps, ETags):
spec.Operation<CreateOrderRequest, ApiResult<Order>>("CreateOrder", (request, state) =>
{
return Expect.That<ApiResult<Order>>(
r => r.IsSuccess && !string.IsNullOrEmpty(r.Data.OrderId))
.ThenState<AppState>(
// Lambda receives response AND a pre-cloned nextState
(ApiResult<Order> response, AppState nextState) =>
nextState.Orders[response.Data.OrderId] = new OrderState
{
Product = request.Product,
Status = OrderStatus.Created
},
// Mock for test generation (no real system running)
mock: () => new ApiResult<Order>
{
Data = new Order
{
OrderId = Guid.NewGuid().ToString(),
Product = request.Product
},
StatusCode = 201
});
});
Why the Mock?
During test generation, there's no real server. The mock provides a plausible response so Accordant can explore the state space. At test execution, the mock is ignored — real responses are used.
Expect.That<OrderResponse>(response =>
{
var errors = new List<string>();
if (response.OrderId == null)
errors.Add("OrderId was null");
if (response.Items.Count != expectedCount)
errors.Add($"Expected {expectedCount} items, got {response.Items.Count}");
if (response.Total != expectedTotal)
errors.Add($"Expected total {expectedTotal}, got {response.Total}");
return errors.Count == 0
? ValidationResult.Valid()
: ValidationResult.Invalid(string.Join("; ", errors));
})
Tuple Requests
For operations with multiple parameters:
// Define with tuple
spec.Operation<(string AccountId, decimal Amount), ApiResult<decimal>>("Withdraw", (request, state) =>
{
var (accountId, amount) = request; // Destructure// ... logic using accountId and amount
});
// Create inputsvar inputs = new InputSet
{
spec.GetOperation<(string, decimal), ApiResult<decimal>>("Withdraw")
.With(("alice", 50m), "Withdraw 50 from alice"),
};
// Bind execution
spec.ExecuteWith<BankApiClient>()
.Bind<(string, decimal), ApiResult<decimal>>("Withdraw",
(client, req) => client.Withdraw(req.Item1, req.Item2).Result);
State Reset Patterns
Delete Known Entities
var knownIds = new[] { "alice", "bob", "9am", "10am" };
var results = await spec.RunTests(context, initialState, testCases, new TestExecutionOptions
{
BeforeEachAsync = async _ =>
{
foreach (var id in knownIds)
{
try { await client.Delete(id); }
catch { /* Ignore 404 */ }
}
}
});
Recreate Test Container
var results = await spec.RunTests(context, initialState, testCases, new TestExecutionOptions
{
BeforeEachAsync = async _ =>
{
await _testContainer.ResetAsync();
}
});
Unique Names Per Test Run
var testRunId = Guid.NewGuid().ToString("N")[..8];
var inputs = new InputSet
{
createOp.With($"user-{testRunId}-1", "Create user 1"),
createOp.With($"user-{testRunId}-2", "Create user 2"),
};
Next Steps
Troubleshooting: Common mistakes and debugging
Test Generation: Configure exploration and algorithms