This directory contains integration tests for the Miden client library. These tests verify the functionality of the client against a running Miden node.
- Parallel Execution: Run tests in parallel to significantly reduce total execution time
- Test Filtering: Filter tests by name patterns, categories, or exclude specific tests
- Flexible Configuration: Configurable RPC endpoints, timeouts, and parallel job counts
- Comprehensive Reporting: Detailed test results with timing statistics and progress tracking
- cargo-nextest-like Experience: Similar filtering and execution patterns as cargo-nextest
To install the integration tests binary:
make install-testsThis will build and install the miden-client-integration-tests binary to your system.
The integration tests binary can be run with various command-line options:
miden-client-integration-tests [OPTIONS]-n, --network <NETWORK>- Network preset:devnet,testnet,localhost, or a custom RPC endpoint (default:localhost). Sets defaults for all components (RPC, prover, note transport)-t, --timeout <MILLISECONDS>- Timeout for RPC requests in milliseconds (default:10000)--prover-url <URL>- Override prover endpoint. Acceptsdevnet,testnet,localhost, or a custom URL. If unset, defaults based on network--note-transport-url <URL>- Override note transport endpoint. Acceptsdevnet,testnet, or a custom URL. If unset, defaults based on network-j, --jobs <NUMBER>- Number of tests to run in parallel (default: auto-detected CPU cores, set to1for sequential execution)-f, --filter <REGEX>- Filter tests by name using regex patterns--contains <STRING>- Only run tests whose names contain this substring--exclude <REGEX>- Exclude tests whose names match this regex pattern--retry-count <NUMBER>- Number of times to retry failed tests (default:3, set to0to disable retries)--list- List all available tests without running them-h, --help- Show help information-V, --version- Show version information
Run all tests with default settings (auto-detected CPU cores):
miden-client-integration-testsRun tests sequentially (no parallelism):
miden-client-integration-tests --jobs 1Run tests with custom parallelism:
miden-client-integration-tests --jobs 8List all available tests without running them:
miden-client-integration-tests --listRun only client-related tests:
miden-client-integration-tests --filter "client"Run tests containing "fpi" in their name:
miden-client-integration-tests --contains "fpi"Exclude swap-related tests:
miden-client-integration-tests --exclude "swap"Run tests against devnet:
miden-client-integration-tests --network devnetRun tests against testnet:
miden-client-integration-tests --network testnetRun tests against devnet (auto-configures remote prover):
miden-client-integration-tests --network devnetRun tests against testnet with a local prover override:
miden-client-integration-tests --network testnet --prover-url localhostRun tests against a custom RPC endpoint with timeout:
miden-client-integration-tests --network http://192.168.1.100:57291 --timeout 30000Complex example: Run non-swap tests in parallel excluding swap tests:
miden-client-integration-tests --exclude "swap"Show help:
miden-client-integration-tests --helpThe following environment variables configure both the standalone binary and the cargo test generated wrappers:
TEST_MIDEN_NETWORK- Network preset:devnet,testnet,localhost, or a custom RPC endpoint URL (default:localhost). Sets defaults for all componentsTEST_MIDEN_RPC_URL- Overrides the RPC endpoint from the network presetTEST_MIDEN_PROVER_URL- Overrides the prover:devnet,testnet,localhost, or a custom URL (default: derived from network)TEST_MIDEN_NOTE_TRANSPORT_URL- Overrides note transport:devnet,testnet, or a custom URL (default: derived from network)MIDEN_TEST_TIMEOUT- Test timeout in milliseconds (default:10000)
| Network | RPC | Prover | Note Transport |
|---|---|---|---|
testnet |
rpc.testnet.miden.io |
tx-prover.testnet.miden.io |
transport.miden.io |
devnet |
rpc.devnet.miden.io |
tx-prover.devnet.miden.io |
transport.devnet.miden.io |
localhost |
localhost:57291 |
localhost | (none) |
Any individual env var overrides the corresponding component from the preset. For example:
# Use testnet defaults but force local prover
TEST_MIDEN_NETWORK=testnet TEST_MIDEN_PROVER_URL=localhost cargo test
# Use devnet RPC with a custom note transport
TEST_MIDEN_NETWORK=devnet TEST_MIDEN_NOTE_TRANSPORT_URL=http://localhost:57292 cargo testFor the standalone binary, CLI flags (--network, --prover-url, --note-transport-url, --timeout) take precedence over environment variables.
The integration tests cover several categories:
- Client: Basic client functionality, account management, and note handling
- Custom Transaction: Custom transaction types and Merkle store operations
- FPI: Foreign Procedure Interface tests
- Network Transaction: Network-level transaction processing
- Onchain: On-chain account and note operations
- Swap Transaction: Asset swap functionality
- AggLayer: AggLayer bridge integration (GER updates, bridge-in/out)
AggLayer tests verify the bridge integration flow: GER updates, faucet registration, bridge-in (claiming), and bridge-out.
The four AggLayer accounts are always supplied rather than created by the tests: they are network accounts, which no client transaction can deploy, and on a fee-charging chain they must be seeded with the fee asset because no note in their allowlist can carry it to them later.
scripts/start-test-node.sh writes them into ./data/:
bridge_admin.mac- Bridge admin wallet (includes secret key)ger_manager.mac- GER manager wallet (includes secret key)bridge.mac- AggLayer bridge account (no secret key, network account)agglayer_faucet.mac- AggLayer faucet account (no secret key, network account)
The bridge is deployed unconfigured. The tests register the faucet against it with a
CONFIG_AGG_BRIDGE note, using a deterministic test origin token address (0xAAAA...AA).
AGGLAYER_ACCOUNTS_DIR- Directory holding the AggLayer.macaccount files. Required by the AggLayer tests. The Make targets point it at./data, where the testing node writes them. On devnet, point it at wherever the devnet account files are stored.
make start-node-background
AGGLAYER_ACCOUNTS_DIR=./data miden-client-integration-tests --contains "agglayer"The same tests work against devnet by setting AGGLAYER_ACCOUNTS_DIR to the directory containing devnet-specific .mac files and using the appropriate RPC endpoint:
AGGLAYER_ACCOUNTS_DIR=/path/to/devnet/accounts \
miden-client-integration-tests --network devnet --contains "agglayer"The testing node charges a fee for every transaction, as a real chain does: its genesis sets
verification_base_fee = 500. To run it fee-free instead:
MIDEN_VERIFICATION_BASE_FEE=0 make start-node-backgroundThe suite never mints. It draws the native asset from pre-funded basic wallets named by --funders
(or MIDEN_FUNDER_ACCOUNTS_DIR), either one .mac file or a directory of them. A path naming no
.mac file means no funders (useful for fee-free chains) so the Make targets can pass the
same path either way.
# Local node: its genesis pre-funds the wallets and start-test-node.sh writes them here.
# The Makefile targets pass this for you.
MIDEN_FUNDER_ACCOUNTS_DIR=$PWD/data/funders cargo nextest run --workspace --release --test=integration
# Deployed network: supply wallets funded out of band, since nothing can be minted there.
miden-client-integration-tests --network testnet --funders ./testnet-fundersThe insert_new_* helpers pay each account they create. The account is deployed by whichever
transaction first consumes that note, as described below. An account a test builds itself can be
funded and deployed on the spot with TestClient::deploy_account, whose deploy transaction
consumes the funding note and thereby settles its own fee.
A funding transaction costs a fee and a proof, so accounts are funded in batches wherever a test
creates more than one: the setup_* helpers create their accounts with the insert_new_*_unfunded
variants and then pass the whole set to TestClient::fund_if_needed, which pays them all from one
transaction. A test creating several accounts of its own should do the same rather than calling the
funding insert_new_* helpers in a row.
Funding costs no transaction of its own beyond that payment. Each account's note is held until the
account's next transaction and folded into it, so that one transaction deploys the account, funds
it and does the test's work. TestClient::submit_new_transaction does the folding; a request going
somewhere else needs TestClient::fund_request first, notably a batch, which borrows the client
for as long as it lives. A test that needs the funding to land in a particular transaction — one
asserting on what a sync reports, say — should call TestClient::take_funding and consume the note
itself.
Funders must be public and carry their secret key: a public funder's state is read from the chain by whichever process claims it, which is what makes sharing one between test processes safe.
MIDEN_FUNDER_ACCOUNTS_DIR- funder.macfile or directory, same as--funders. Unset, empty or naming no.macfile leaves the run without fundersMIDEN_VERIFICATION_BASE_FEE- genesisverification_base_feefor the testing node (default500;0runs the node fee-free and declares no funder wallets)MIDEN_NUM_FUNDER_WALLETS- number of wallets a fee-charging genesis pre-funds (default16)
The integration tests use an automatic code generation system to create both cargo nextest compatible tests and a standalone binary. Test functions that start with test_ are automatically discovered during build time and used to generate:
- Individual
#[tokio::test]wrappers - These allow the tests to be run using standardcargo testorcargo nextest runcommands - Programmatic test access - A
Vec<TestCase>that enables the standalone binary to enumerate and execute tests dynamically with custom parallelism and filtering
The discovery system:
- Scans all
.rsfiles in thesrc/directory recursively - Identifies functions named
test_*(supportingpub async fn test_*,async fn test_*, etc.) - Generates test registry and integration test wrappers automatically
This dual approach allows the same test code to work seamlessly with both nextest (for development) and the standalone binary (for CI/CD and production testing scenarios), ensuring consistent behavior across different execution environments.
To add a new integration test:
- Create a public async function that starts with
test_ - The function should take a
ClientConfigparameter - The function should return
Result<()> - Place the function in any
.rsfile undersrc/
Example:
pub async fn test_my_feature(client_config: ClientConfig) -> Result<()> {
let (mut client, authenticator) = client_config.into_client().await?;
// test logic here
}The build system will automatically discover this function and include it in both the test registry and generate tokio test wrappers.
This project is MIT licensed.