Overview
Actors are deterministic Python, which makes them very testable: the SDK can stub the entire PVM host API in-process, so handlers run as plain functions under pytest — no validator, no Docker, sub-second feedback.
This guide covers the three layers of the testing ladder:
- Unit tests with
SimulatedChain — state, events, timers, determinism
- Mocking cross-actor calls with
CallMock
- Integration runs against a real devnet
1. Unit testing with SimulatedChain
cowboy_sdk.testing.SimulatedChain is an in-memory, single-actor harness built on cowboy_sdk.mock_host. Constructing one installs the mock host (so import pvm_host resolves to it), resets state, and gives you a driveable chain:
What the harness gives you:
Testing timers
advance_block fires every timer whose height is due and routes it to the handler named in the timer payload — so scheduled behavior is testable without a validator:
Determinism checks
Same scenario, two fresh chains, identical final state — a cheap guard against accidentally nondeterministic code (iteration order, time, randomness):
2. Mocking cross-actor calls
When your actor calls other actors, stub the responses with CallMock — a matcher-based mock for the host’s call operation. Note that cowboy_sdk.call() CBOR-encodes arguments and CBOR-decodes responses, so matchers and responses are expressed in encoded bytes:
Rules match on any combination of target, method, args (exact equality on the encoded bytes) or a custom predicate; unmatched calls get the default. Every call is recorded in mock.calls as (target, method, args, cycles_limit).
3. Wiring it into pytest
Install the mock host early — before any actor module is imported — so every pvm_host lookup resolves to it. SimulatedChain does this on construction; for suites that import actor modules at collection time, do it in conftest.py:
Then run with the SDK available in your Python test environment. If you are working from a full source checkout, expose only the cowboy_sdk package; do not add the PVM’s entire Python standard library to PYTHONPATH, because it can shadow CPython’s stdlib and break pytest:
mock_host.reset() between tests is essential — state, timers, and context are module-level in the mock. The autouse fixture above keeps tests independent.
mock_host.set_context(...) lets you fake execution-context fields (sender, tx hash, …) when a handler branches on them; for the block clock, use set_block_height() (or SimulatedChain’s helpers), which is what timers and runtime.get_block_height() follow.
4. Integration: run against a real devnet
Unit tests don’t exercise CBOR boundaries with the real host, gas metering, or the scheduler’s actual dispatch. Once unit tests pass, run the actor on the configured devnet:
For source-checkout examples there is a shared sweep harness that can spawn a temporary isolated validator per run:
What to test at which layer
Further reading