ic-testkit 0.0.2

PocketIC-oriented test utilities for IC canister tests
Documentation

ic-testkit

Small, reusable test helpers around pocket-ic for Internet Computer canister tests.

Crates.io Docs.rs Downloads License MSRV Internal Rust Edition PocketIC Repository

ic-testkit is for host-side Rust tests that need a little structure on top of raw pocket-ic: typed Candid calls, install helpers, diagnostics, serialized PocketIC startup, cached baselines, deterministic fake principals, and wasm artifact utilities.

It is intentionally application-neutral. Bring your own init payloads, method names, readiness checks, fixture graph, and product-specific test policy.

Install

[dev-dependencies]
ic-testkit = "0.0.1"

Quick Start

Use PicSerialGuard when a test owns a PocketIC instance. It serializes PocketIC usage across processes, which helps avoid shared server/resource exhaustion in larger test runs.

use ic_testkit::pic::{acquire_pic_serial_guard, pic};

#[test]
fn starts_a_pic_instance() {
    let _guard = acquire_pic_serial_guard();
    let pic = pic();

    let canister_id = pic.create_canister();
    pic.add_cycles(canister_id, 1_000_000_000_000);
    pic.tick();
}

Calling Canisters

Pic wraps common update/query calls with Candid encoding and decoding. The error includes the canister id and method name when PocketIC rejects the call.

use ic_testkit::pic::{acquire_pic_serial_guard, pic};

#[test]
fn calls_a_counter_canister() {
    let _guard = acquire_pic_serial_guard();
    let pic = pic();
    let counter = install_counter(&pic);

    let _: () = pic.update_call(counter, "increment", ()).unwrap();
    let value: u64 = pic.query_call(counter, "get", ()).unwrap();

    assert_eq!(value, 1);
}

Use the _as variants when the caller matters:

use candid::Principal;

let caller = ic_testkit::Fake::principal(7);
let ledger_id = Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap();

let balance: u128 = pic
    .query_call_as(ledger_id, caller, "balance_of", (caller,))
    .unwrap();

Installing Wasm

For one-off tests, install a prebuilt wasm into a fresh PocketIC instance:

use ic_testkit::{artifacts, pic::install_prebuilt_canister};

#[test]
fn installs_a_prebuilt_canister() {
    let workspace = artifacts::workspace_root_for(env!("CARGO_MANIFEST_DIR"));
    let target = artifacts::test_target_dir(&workspace, "pic-wasm");
    let wasm = artifacts::read_wasm(
        &target,
        "counter_canister",
        artifacts::WasmBuildProfile::Release,
    );

    let fixture = install_prebuilt_canister(wasm, vec![]);
    fixture.pic().tick();
}

For existing Pic instances, use the lower-level helper:

let canister_id = pic.create_and_install_with_args(
    wasm,
    candid::encode_one(()).unwrap(),
    1_000_000_000_000,
);

If PocketIC reports install-code rate limiting, retry while advancing PocketIC time between attempts:

use std::time::Duration;

let result = pic.retry_install_code_ok(5, Duration::from_secs(5), || {
    pic.try_create_and_install_with_args(wasm.clone(), vec![], 1_000_000_000_000)
        .map_err(|err| err.to_string())
});

Artifact Helpers

Build wasm packages into a dedicated target directory:

use ic_testkit::artifacts::{self, WasmBuildProfile};

let workspace = artifacts::workspace_root_for(env!("CARGO_MANIFEST_DIR"));
let target = artifacts::test_target_dir(&workspace, "pic-wasm");

artifacts::build_wasm_canisters(
    &workspace,
    &target,
    &["counter_canister"],
    WasmBuildProfile::Release,
    &[],
);

assert!(artifacts::wasm_artifacts_ready(
    &target,
    &["counter_canister"],
    WasmBuildProfile::Release,
));

Check generated .icp artifacts against watched inputs:

let ready = artifacts::icp_artifact_ready_for_build(
    &workspace,
    ".icp/local/canisters/counter/counter.wasm.gz",
    &["Cargo.toml", "src"],
);

Deterministic Test Identities

Fake gives stable principals and account-like values from numeric seeds:

use ic_testkit::Fake;

let alice = Fake::principal(1);
let bob = Fake::principal(2);
let account = Fake::account(42);

assert_ne!(alice, bob);
assert_eq!(account.owner, Fake::principal(42));

Cached Baselines

For expensive multi-canister setup, CachedPicBaseline can snapshot canisters once and restore them between tests. If the cached PocketIC instance has died, restore_or_rebuild_cached_pic_baseline rebuilds instead of reusing a broken instance.

Use this when setup time dominates the test and the fixture can be restored from PocketIC snapshots. Keep application-specific topology and readiness logic in your own test harness.

What This Adds Over pocket-ic

  • Pic, a narrow wrapper for the PocketIC operations used by this crate
  • typed startup errors for common PocketIC launch failures
  • PicSerialGuard for cross-process PocketIC serialization
  • Candid query/update helpers with contextual errors
  • generic wasm install helpers and install-code retry helpers
  • canister status/log diagnostics
  • standalone prebuilt-wasm fixtures
  • cached snapshot baselines
  • deterministic fake principals and accounts
  • wasm path/build/readiness helpers
  • watched-input freshness checks for generated .icp artifacts

Boundaries

This crate does not define application init payloads, endpoint names, role models, readiness polling, canister graph topology, attestation policy, or broad self-test orchestration. Those belong in the application or framework that owns the canisters being tested.

Toolchains

  • MSRV: Rust 1.88
  • internal build/test lane: Rust 1.95

Local Checks

make test
cargo +1.88.0 check
cargo publish --dry-run