Raw capture โ raw/microsoft-accordant.md
url: https://github.com/microsoft/accordant date_fetched: 2026-07-05 backfilled: true
Executable behavioral specifications for .NET
Documentation ยท NuGet ยท Samples
Accordant is a framework for model-based testing. You write a spec โ executable code that captures the rules of your system. Given any state and any operation, the spec defines what the response should be and how the state should change. Accordant then generates hundreds of tests from this spec, runs them against your real implementation, and validates every response โ telling you exactly where the implementation deviates from the rules.
Say you're building a banking service. You write tests like this:
[Test]
public async Task Withdraw_WithSufficientBalance_Succeeds()
{
await client.CreateAccount("alice");
await client.Deposit("alice", 100);
var result = await client.Withdraw("alice", 30);
Assert.True(result.IsSuccess);
Assert.Equal(70, result.Data);
}
Now imagine thirty more โ insufficient balance, nonexistent account, duplicate creates, deposits after deletes, different orderings. Each test carries its own assertions, but they're all expressing pieces of the same contract: the rules for how your system behaves. Those rules end up scattered across your test suite, repeated in slightly different forms.
What if you wrote them once?
Here's the complete contract for Withdraw, in one place:
spec.Operation<WithdrawRequest, WithdrawResponse>("Withdraw", (request, state) =>
{
if (!state.Accounts.TryGetValue(request.AccountId, out var balance))
return Expect.That(r => r.IsNotFound).SameState();
if (balance < request.Amount)
return Expect.That(r => r.IsBadRequest).SameState();
var newBalance = balance - request.Amount;
return Expect.That(r => r.IsSuccess && r.Balance == newBalance)
.ThenState<BankState>(s => s.Accounts[request.AccountId] = newBalance);
});
spec.Operation registers an operation. The lambda receives the request and current state, and returns what the response should look like (Expect.That(...)) paired with how the state should change (.SameState() or .ThenState(...)). Read it as a truth table: account doesn't exist โ not-found, state unchanged; insufficient balance โ bad-request, state unchanged; otherwise โ success with new balance, state updated.
The state is whatever information you need to define what correct behavior means. For banking, that's just accounts and their balances:
[State]
public partial class BankState
{
public Dictionary<string, decimal> Accounts { get; set; } = new();
}
The [State] attribute triggers source generation for cloning and equality โ you define the data structure, Accordant handles the rest. The state is intentionally simpler than the real implementation. We treat the system as a black box; we don't care whether data lives in SQL Server, Redis, or a flat file.
โ See the full BankAccount spec
Provide a few sample inputs โ some account IDs, some amounts:
// Operation handles obtained via spec.GetOperation<TRequest, TResponse>(name)
var inputs = new InputSet
{
createAccount.With(new CreateAccountRequest("alice"), "Create(alice)"),
deposit.With(new DepositRequest("alice", 50m), "Deposit(alice, 50)"),
deposit.With(new DepositRequest("alice", 100m), "Deposit(alice, 100)"),
withdraw.With(new WithdrawRequest("alice", 30m), "Withdraw(alice, 30)"),
withdraw.With(new WithdrawRequest("alice", 70m), "Withdraw(alice, 70)"),
deleteAccount.With(new DeleteAccountRequest("alice"), "Delete(alice)"),
};
Because the spec defines what each operation does to state, Accordant can simulate the system โ predicting what happens without running real code. Starting from an empty state, it tries every operation with every input. Operations that change the state produce new nodes (e.g., creating an account that doesn't exist); operations that don't change state loop back to the same node (e.g., withdrawing from a nonexistent account). From each new node, it tries every operation again. The reachable states naturally unfold into a graph:
Accordant then picks paths through this graph as test sequences:
| # | Sequence | Category |
|---|---|---|
| 1 | Create(alice)โDeposit(alice, 100)โWithdraw(alice, 70) |
โ success path |
| 2 | Create(alice)โDeposit(alice, 50)โWithdraw(alice, 70) |
โ insufficient funds |
| 3 | Withdraw(alice, 30) |
โ 404 on non-existent account |
| 4 | Create(alice)โCreate(alice) |
โ 409 duplicate |
| 5 | Create(alice)โDeposit(50)โDelete(alice) |
โป lifecycle |
| 6 | Create(alice)โDeposit(100)โDeposit(50) |
โ accumulate balance |
These aren't random โ they're systematic walks designed to exercise different branches. Each sequence is run against the real system, and the spec validates every response along the way:
Generated 31 test cases
Executed against BankAccount API
Results: 31 passed, 0 failed
From six sample inputs, Accordant generated thirty-one test cases that cover the reachable state space, with every response validated automatically.
The spec separates two concerns that traditional tests tangle together: deciding what sequence to run and deciding whether the result is correct. The spec handles the second part โ it's an oracle. You bring whatever sequences you like.
That means you can pair the oracle with any source of test sequences:
In all cases, validation is the same single call:
var (isValid, message, nextState) = spec.Allows(operation, request, response, currentState);This also improves your existing hand-written tests. Compare โ today each test carries bespoke assertions that duplicate business logic:
var r1 = await client.CreateAccount("alice");
Assert.True(r1.IsSuccess);
var r2 = await client.Deposit("alice", 100);
Assert.True(r2.IsSuccess);
Assert.Equal(100, r2.Balance); // โ business rule repeated here
var r3 = await client.Withdraw("alice", 30);
Assert.True(r3.IsSuccess);
Assert.Equal(70, r3.Balance); // โ and here
With the spec as oracle, the same test becomes:
var r1 = await client.CreateAccount("alice");
spec.Validate(createOp, request1, r1); // โ one call, all rules checked
var r2 = await client.Deposit("alice", 100);
spec.Validate(depositOp, request2, r2); // โ same call, same rules
var r3 = await client.Withdraw("alice", 30);
spec.Validate(withdrawOp, request3, r3); // โ same call, same rules
The test is now just a sequence โ it says what operations to run, and the spec says whether the responses are correct. When the contract changes, you update the spec once and every test that uses it automatically validates against the new rules.
Reviewable โ Business rules live in one place: 60 lines of clear logic rather than scattered across hundreds of assertions. Reviewing a spec change is easier than reviewing changes to dozens of test files โ you see the rule, not the repetition.
Confidence through change โ Refactor the implementation freely. When requirements change, update the spec and test generation adapts automatically. No hunting through test files to find every assumption that needs updating.
Natural fit with AI โ A spec lets you precisely state what you want and mechanically verify that you got it โ through automatically generated test cases that check the implementation against your intent. AI can help write the spec, and the spec provides strong guardrails when AI generates the implementation.
The same spec enables other kinds of testing:
Install the package (NuGet):
dotnet add package Microsoft.AccordantWant an AI assistant to guide you?
Point your AI coding agent (Copilot, Cursor, Claude Code, etc.) at agent/INSTALL.md and it will walk you through setup and your first spec โ interactively, using your actual project and domain.
Just want to see it work? Clone the repo and run the BankAccount sample:
git clone https://github.com/microsoft/accordant.git
cd accordant/Samples/BankAccount/BankAccount.Api.Tests
dotnet test
You'll see output like:
Passed AutoGeneratedSequentialTests [2 s]
Passed HandWrittenScenarios [52 ms]
Passed VisualizeStateSpace [20 ms]
Building your first spec?
Going deeper?
| Type | What it covers |
|---|---|
| Tutorials | Step-by-step guides to learn Accordant |
| Concepts | Understand the theory โ model-based testing, linearizability, state graphs |
| How-To Guides | Solve specific problems โ "how do I reset state between tests?" |
| Samples | Working code โ BankAccount, TodoList, and more |
| API Reference | Complete API documentation |
Have questions, ideas, or want to share what you're building? Join the conversation on GitHub Discussions.
See CONTRIBUTING.md for build instructions and contribution guidelines.
This project has adopted the Microsoft Open Source Code of Conduct.