| name | accordant-quickref |
| description | Quick reference and cheatsheet for Accordant API - use this skill for quick syntax lookup or code snippets |
Accordant Quick Reference
State Definition
[State]
public partial class MyState
{
public Dictionary<string, decimal> Balances { get; set; } = new();
public Dictionary<string, UserState> Users { get; set; } = new();
}
[State]
public partial class UserState
{
public string Name { get; set; } = string.Empty;
public List<string> Tags { get; set; } = new();
}
Create a Spec
var spec = new Spec<MyState>()
.WithJsonPrinters();
Define Operations
Inline Syntax
spec.Operation<TRequest, TResponse>("OperationName", (request, state) =>
{
});
Examples
spec.Operation<string, ApiResult<User>>("GetUser", (userId, state) => { ... });
spec.Operation<(string Id, decimal Amount), ApiResult<decimal>>("Deposit", (request, state) =>
{
var (id, amount) = request;
});
spec.Operation<CreateUserRequest, ApiResult<User>>("CreateUser", (request, state) => { ... });
Expected Outcomes
Basic Expectation
Expect.That<TResponse>(response => )
With Explanation (Recommended)
Expect.That<ApiResult<decimal>>(
r => r.IsSuccess && r.Data == expectedValue,
$"Expected success with value {expectedValue}")
With ValidationResult
Expect.That<User>(response =>
{
if (response.Name != expected)
return ValidationResult.Invalid($"Name mismatch: {response.Name}");
return ValidationResult.Valid();
})
Exception Expectation
Expect.Throws<NotFoundException>("Resource not found")
Multiple Valid Outcomes
Expect.OneOf(
Expect.That<T>(r => ).ThenState(...),
Expect.That<T>(r => ).SameState(),
Expect.That<T>(r => ).ThenState(...)
)
State Transitions
No Change
.SameState()
State Changes
.ThenState<MyState>(nextState => nextState.Items[id] = newValue)
Response-Dependent State
.ThenState<MyState>(
(TResponse response, MyState nextState) =>
nextState.Items[response.Id] = new Item { ... },
mock: () => new TResponse { Id = Guid.NewGuid().ToString() })
Async Operations
.ThenState<MyState>(next => next.Jobs[id] = JobStatus.Pending)
.Triggers(AsyncOperation.Create<MyState>(
isTerminal: s => s.Jobs[id] != JobStatus.Pending,
transitions: new Action<MyState>[] {
next => next.Jobs[id] = JobStatus.Completed,
next => next.Jobs[id] = JobStatus.Failed
}
))
Bind Execution
spec.ExecuteWith<ApiClient>()
.Bind<TRequest, TResponse>("OperationName",
(client, request) => client.Method(request).Result)
.BindAsync<TRequest, TResponse>("AsyncOperation",
async (client, request) => await client.MethodAsync(request));
Test Generation
Define Inputs
var inputs = new InputSet
{
spec.GetOperation<string, ApiResult<User>>("CreateUser").With("alice", "Create Alice"),
spec.GetOperation<(string, decimal), ApiResult<decimal>>("Deposit").With(("alice", 100m), "Deposit 100"),
};
Generate Tests
var testCases = spec.GenerateTests(initialState, inputs, new TestGenerationOptions
{
MaxDepth = 5,
StateConstraint = state => ((MyState)state).Items.Count <= 3
});
Run Tests
var context = spec.CreateTestingContext();
context.Register(new ApiClient(httpClient));
var results = await spec.RunTests(context, initialState, testCases, new TestExecutionOptions
{
BeforeEachAsync = async _ => await ResetDatabase()
});
Assert.That(results.All(r => r.Success));
Concurrent Tests
var concurrentTestCases = spec.GenerateConcurrentTests(initialState, inputs,
new TestGenerationOptions { MaxDepth = 4 });
var results = await spec.RunTests(context, initialState, concurrentTestCases,
new TestExecutionOptions { ... });
Manual Conformance Testing
var stateProfile = new StateProfile(initialState);
var response = await client.CreateUser("alice");
(bool isValid, string message, stateProfile) =
spec.Allows(createUserOp, "alice", response, stateProfile);
Assert.IsTrue(isValid, message);
Visualization
var dot = spec.VisualizeStateSpace(initialState, inputs, options);
File.WriteAllText("graph.dot", dot);
TestGenerationOptions
| Option | Type | Default | Description |
|---|
MaxDepth | int | 5 | Max sequence length |
StateConstraint | Func<State, bool> | null | Prune states |
MaxConcurrencyLevel | int | 3 | Concurrent ops |
SequentialTestCaseAlgorithm | delegate | StateCoverage | Walk algorithm |
Test Case Algorithms
SequentialTestCaseAlgorithms.StateCoverage
SequentialTestCaseAlgorithms.CreateTransitionCoverage(maxSequenceLength: 4)
SequentialTestCaseAlgorithms.CreateRandomWalk(numberOfWalks: 100, maxWalkLength: 8, seed: 42)
Common Operation Pattern
spec.Operation<(string Id, decimal Amount), ApiResult<decimal>>("Withdraw", (request, state) =>
{
var (accountId, amount) = request;
if (!state.Accounts.TryGetValue(accountId, out var balance))
return Expect.That<ApiResult<decimal>>(r => r.IsNotFound, "Account not found")
.SameState();
if (balance < amount)
return Expect.That<ApiResult<decimal>>(r => r.IsBadRequest, "Insufficient funds")
.SameState();
var newBalance = balance - amount;
return Expect.That<ApiResult<decimal>>(
r => r.IsSuccess && r.Data == newBalance,
$"Expected new balance {newBalance}")
.ThenState<BankState>(next => next.Accounts[accountId] = newBalance);
});
Namespace
using Microsoft.Accordant;
NuGet Package
dotnet add package Microsoft.Accordant