<div align="center">
# ic-testkit
**Small, reusable test helpers around `pocket-ic` for Internet Computer canister tests.**
[](https://crates.io/crates/ic-testkit)
[](https://docs.rs/ic-testkit)
[](https://crates.io/crates/ic-testkit)
[](Cargo.toml)
[](Cargo.toml)
[](README.md#toolchains)
[](Cargo.toml)
[](Cargo.toml)
[](https://github.com/dragginzgame/ic-testkit)
</div>
`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
```toml
[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.
```rust
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.
```rust
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:
```rust
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:
```rust
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:
```rust
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:
```rust
use std::time::Duration;
.map_err(|err| err.to_string())
});
```
## Artifact Helpers
Build wasm packages into a dedicated target directory:
```rust
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:
```rust
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:
```rust
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
```sh
make test
cargo +1.88.0 check
cargo publish --dry-run
```