Skip to content

Checking access...

PocketIC Setup Guide ​

This guide explains how to set up PocketIC for local canister testing.

What is PocketIC? ​

PocketIC is a lightweight, deterministic IC replica for testing Rust canisters locally. It provides:

  • Fast test execution (~1s per test vs ~10s with dfx)
  • Deterministic behavior (no timing issues)
  • Easy setup (single binary, no daemon)
  • Full IC behavior simulation

Installation ​

bash
# Check latest release at https://github.com/dfinity/pocketic/releases
# Download for your platform:

# Linux (x86_64)
curl -sLO https://github.com/dfinity/pocketic/releases/download/v6.0.0/pocket-ic-x86_64-linux.gz
gunzip pocket-ic-x86_64-linux.gz
mv pocket-ic-x86_64-linux pocket-ic
chmod +x pocket-ic

# macOS (Apple Silicon)
curl -sLO https://github.com/dfinity/pocketic/releases/download/v6.0.0/pocket-ic-aarch64-darwin.gz
gunzip pocket-ic-aarch64-darwin.gz
mv pocket-ic-aarch64-darwin pocket-ic
chmod +x pocket-ic

# Move to canister repo
mv pocket-ic /path/to/your-canister-repo/

Option 2: Copy from Existing Repo ​

If another canister repo already has the binary:

bash
cp <repo-root>/membership/pocket-ic <repo-root>/your-new-canister/

Add to .gitignore ​

The binary is 73MB and should NOT be committed:

bash
echo "pocket-ic" >> .gitignore

Cargo.toml Configuration ​

Add PocketIC as a dev dependency:

toml
[dev-dependencies]
pocket-ic = "6"
candid = "0.10"
serde = { version = "1", features = ["derive"] }

Test File Structure ​

Create tests/pocketic_smoke.rs:

rust
use candid::{decode_one, encode_one, encode_args, Principal};
use pocket_ic::{PocketIc, WasmResult};
use serde::{Deserialize, Serialize};

// =============================================================================
// Helper Functions
// =============================================================================

fn unwrap_wasm_result(result: WasmResult) -> Vec<u8> {
    match result {
        WasmResult::Reply(bytes) => bytes,
        WasmResult::Reject(msg) => panic!("Canister rejected call: {}", msg),
    }
}

fn get_wasm_path() -> String {
    // Try wasm/ first (pre-built), then target/ (fresh build)
    let paths = [
        "wasm/your_canister.wasm",
        "target/wasm32-unknown-unknown/release/your_canister.wasm",
    ];
    for path in paths {
        if std::path::Path::new(path).exists() {
            return path.to_string();
        }
    }
    panic!("WASM not found. Run: cargo build --release --target wasm32-unknown-unknown");
}

fn setup() -> (PocketIc, Principal, Principal) {
    let pic = PocketIc::new();
    let wasm = std::fs::read(get_wasm_path()).expect("Failed to read WASM");

    let controller = Principal::from_text("aaaaa-aa").unwrap();
    let canister_id = pic.create_canister();
    pic.add_cycles(canister_id, 2_000_000_000_000);

    // Install with init args (adjust based on your canister)
    pic.install_canister(
        canister_id,
        wasm,
        encode_one(Some(vec![controller])).unwrap(),
        None,
    );

    (pic, canister_id, controller)
}

// =============================================================================
// Tests
// =============================================================================

#[test]
fn test_health_check() {
    let (pic, canister_id, _) = setup();

    let response = pic.query_call(
        canister_id,
        Principal::anonymous(),
        "health",
        encode_one(()).unwrap(),
    ).unwrap();

    let health: String = decode_one(&unwrap_wasm_result(response)).unwrap();
    assert_eq!(health, "ok");
}

Running Tests ​

Build WASM First ​

bash
cargo build --release --target wasm32-unknown-unknown

Run All PocketIC Tests ​

bash
cargo test

Run Specific Test ​

bash
cargo test test_health_check

Run with Output ​

bash
cargo test -- --nocapture

Common Patterns ​

Testing Query Calls ​

rust
let response = pic.query_call(
    canister_id,
    Principal::anonymous(),  // or specific principal
    "method_name",
    encode_one(arg).unwrap(),
).unwrap();

let result: ReturnType = decode_one(&unwrap_wasm_result(response)).unwrap();

Testing Update Calls ​

rust
let response = pic.update_call(
    canister_id,
    caller_principal,  // Who is calling
    "method_name",
    encode_args((arg1, arg2, arg3)).unwrap(),
).unwrap();

let result: Result<T, String> = decode_one(&unwrap_wasm_result(response)).unwrap();

Testing Access Control ​

rust
fn non_admin_principal() -> Principal {
    Principal::from_text("2vxsx-fae").unwrap()
}

#[test]
fn test_admin_only_rejects_non_admin() {
    let (pic, canister_id, _controller) = setup();

    let response = pic.update_call(
        canister_id,
        non_admin_principal(),  // Not an admin
        "admin_only_method",
        encode_one(()).unwrap(),
    ).unwrap();

    let result: Result<(), String> = decode_one(&unwrap_wasm_result(response)).unwrap();
    assert!(result.is_err());
}

Testing Option Return Types ​

rust
#[test]
fn test_get_not_found() {
    let (pic, canister_id, _) = setup();

    let response = pic.query_call(
        canister_id,
        Principal::anonymous(),
        "get_item",
        encode_one(999u64).unwrap(),  // Non-existent ID
    ).unwrap();

    let result: Option<Item> = decode_one(&unwrap_wasm_result(response)).unwrap();
    assert!(result.is_none());
}

Troubleshooting ​

"pocket-ic: command not found" ​

The binary must be in the repo root directory. PocketIC looks for it relative to the test execution directory.

WASM Not Found ​

Build the WASM first:

bash
cargo build --release --target wasm32-unknown-unknown

Or copy pre-built WASM:

bash
mkdir -p wasm
cp target/wasm32-unknown-unknown/release/your_canister.wasm wasm/

Candid Decoding Errors ​

Ensure your test type definitions exactly match the canister's .did file. Field names, enum variants, and types must match exactly.

Controller Verification Fails ​

PocketIC's controller verification differs slightly from production IC. If testing controller-only methods, ensure you're calling as the controller principal used during pic.create_canister().

References ​

  • PocketIC GitHub
  • PocketIC Rust Crate
  • Example: auth-service tests (auth-service/tests/pocketic_smoke.rs)
  • Example: dao-admin tests (dao-admin/tests/pocketic_smoke.rs)
  • Example: membership tests (membership/tests/pocketic_smoke.rs)

Hello World DAO