Unit Testing with ContractingClient
ContractingClient compiles and executes contracts locally without a node. Use it to call exports, change signers, inspect state, and test rollback.
Setup
uv add xian-tech-contracting pytestimport pytest
from contracting.local import ContractingClient
@pytest.fixture
def client():
local = ContractingClient()
local.flush()
yield local
local.flush()Submit and Call
def token_contract():
balances = Hash(default_value=0)
@construct
def seed(initial_supply: int):
balances[ctx.caller] = initial_supply
@export
def transfer(amount: float, to: str):
assert amount > 0, "Amount must be positive"
assert balances[ctx.caller] >= amount, "Insufficient balance"
balances[ctx.caller] -= amount
balances[to] += amount
@export
def balance_of(address: str) -> float:
return balances[address]
def test_transfer(client):
client.submit(
token_contract,
name="con_token",
constructor_args={"initial_supply": 1_000},
)
token = client.get_contract_proxy("con_token")
token.transfer(to="alice", amount=100)
assert token.balance_of(address="alice") == 100
assert client.get_var("con_token", "balances", arguments=["sys"]) == 900The default signer is sys. Override it for a call when testing another account:
token.transfer(to="bob", amount=25, signer="alice")A proxy captures client.signer when the proxy is created. If you change the client-wide signer instead, create a new proxy afterward.
Direct State Helpers
Use direct state access only for assertions and controlled test setup:
owner = client.get_var("con_app", "owner")
balance = client.get_var("con_token", "balances", arguments=["alice"])
client.set_var("con_token", "balances", arguments=["alice"], value=500)These helpers are local-test APIs, not network mutation endpoints.
Test Failures and Rollback
with pytest.raises(AssertionError, match="Insufficient balance"):
token.transfer(to="bob", amount=10_000)After a failed call, assert that state and emitted events were not partially committed.
Metering
Pass metering=True when a test needs local chi measurements. Treat those values as regression signals; use node simulation for estimates against the effective network runtime and configuration.
Isolation
Create a fresh client per test or call flush() between tests. Do not let contracts, state, signer selection, or environment overrides leak across test cases.