Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

ElementMeaning
test "name" for EntityDeclaration; entity must exist
skip fromDisable 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 = exprBinding 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 EAssert 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).
  • ctxmsg::sender, msg::value, sys::now, sys::balance, …. Putting msg:: inside with is rejected (T22).

Plain test blocks historically also use msg { } / sys { } bodies; properties and invariants prefer the explicit ctx { } record. See Properties.