Unit Tests
A test declares a concrete scenario against one entity: optional initial state, message/system setup, one or more calls, and expect* assertions.
Syntax
test "fund changes state" for Escrow {
let buyer = 0x0000000000000000000000000000000000000001
let seller = 0x0000000000000000000000000000000000000002
let arbiter = 0x0000000000000000000000000000000000000003
call constructor(buyer, seller, arbiter, 1000, 9999)
expect state { m_state: 0 }
msg { sender: buyer }
call fund()
expect state { m_state: 1, m_funded: true }
}
Modifier order when skipping sender checks:
test "…" for <Entity> skip from with { … } { … }
skip from sits between the entity name and the optional with block.
Building blocks
| Element | Meaning |
|---|---|
test "name" for Entity | Declaration; entity must exist |
skip from | Disable all from-clause sender checks in this test |
with { field: value, … } | Initial entity state (members only) |
ctx { msg::x: …, sys::y: … } | Preferred place for blockchain context on properties/invariants; plain tests often still use inline msg { } / sys { } blocks |
let name = expr | Binding visible for the rest of the test |
msg { … } / sys { … } | Per-step message / system context overrides |
call route(args) | Invoke a route (arity must match) |
expect state { … } | Assert members after a call |
expect throw N / expect throw Name(…) | Assert revert / error |
expect return E | Assert return payload (route must declare -> T) |
expect effects […] | Assert outgoing effects (backend-dependent; prefer state/return on EVM) |
On Lean, every expect after a call is kept and conjoined in the theorem goal.
Lenses
Nested fields, map keys, and tuple slots use path syntax:
expect state { m_pools[0].reserve_a: 1500 }
expect state { m_allowances[owner][spender]: 500 }
expect state { m_pair.0: 10 }
expect return.0 == 0
expect return.len == 3
expect return[0].name == 42
Enums / options:
expect state { m_state: State::Funded }
expect state { m_resolution: some("resolved") }
HashMap literals:
expect state { m_balances: { alice => 900, bob => 100 } }
expect state { m_balances[alice]: 900 }
Multi-step tests
State threads automatically: output of one call is input to the next, with or without an intervening expect.
test "multi-step" for Counter with { m_count: 0 } {
call increment(1)
expect state { m_count: 1 }
call increment(1)
expect state { m_count: 2 }
}
skip from vs authentic senders
Routes with from Entity(…) verify msg.sender. For unit tests of business logic, skip from disables that check. For end-to-end sender checks on EVM, set msg { sender: … } to the address the from clause expects (often Entity.address(…) under deterministic mode).
with vs ctx (brief)
with/ state pins — entity members (m_count: 0).ctx—msg::sender,msg::value,sys::now,sys::balance, …. Puttingmsg::insidewithis rejected (T22).
Plain test blocks historically also use msg { } / sys { } bodies; properties and invariants prefer the explicit ctx { } record. See Properties.