Skip to main content

PocketIC

Beginner
Testing canisters

PocketIC is a lightweight, deterministic testing solution for programmatic testing of canisters. It can be used to simulate mainnet behavior in a local development environment for more deterministic testing.

dfx start uses PocketIC by default to run a local development environment as of v0.26.0.

It can also be run as a standalone binary for automated testing on macOS and Linux systems that doesn't require additional containers or virtual machines.

PocketIC provides synchronous control over the local development environment by removing the non-deterministic parts of the replica to make tests fully reproducible. It only provides the necessary components and strips away the consensus and networking layers.

PocketIC can be used to run concurrent and independent IC instances so that tests can run in parallel. It supports XNet calls and simulating multiple subnets locally. Using PocketIC, you can run tests that set stable memory, control how time passes, and other testing environment variables.

Currently, it supports client libraries for Rust, Python, and JavaScript/TypeScript and can support integration with any language that is written against the PocketIC REST API.

How to use PocketIC

There are two ways to use PocketIC:

  1. PocketIC can be used to create a local development environment with dfx start. Canisters deployed to this local environment can be interacted with via dfx commands or through their Candid UI.

In dfx versions v0.26.0 and newer, PocketIC is the default local development environment.

For older dfx versions, PocketIC must be manually enabled with dfx start --pocketic.

  1. You can create automated tests through a PocketIC client library. In this workflow, the client library starts the PocketIC server using the binary file located at the file path stored as the POCKET_IC_BIN environment variable.

Using PocketIC with dfx start

To learn about running local development environments with dfx, learn more about dfx start.

Using PocketIC client libraries for automated tests

View a minimalistic example of setting up PocketIC tests in a Rust project.

Run the following command to install the PocketIC cargo package:

cargo add pocket-ic --dev

Import PocketIC into your test file:

test.rs
use pocket_ic::PocketIc;

Create a new PocketIC instance:

test.rs
let pic = PocketIc::new();

Create a simple test that creates a canister and makes a call to it:

test.rs
// Create a counter canister and charge it with 2T cycles.
fn deploy_counter_canister(pic: &PocketIc) -> Principal {
let canister_id = pic.create_canister();
pic.add_cycles(canister_id, 2_000_000_000_000);
pic.install_canister(canister_id, counter_wasm(), vec![], None);
canister_id
}

// Call a method on the counter canister as the anonymous principal.
fn call_counter_canister(pic: &PocketIc, canister_id: Principal, method: &str) -> Vec<u8> {
pic.update_call(
canister_id,
Principal::anonymous(),
method,
encode_one(()).unwrap(),
)
.expect("Failed to call counter canister")
}

Hello, world! test

src/backend/tests/integration_tests.rs
loading...

Simple counter canister on a single subnet

counter_test.rs
use candid::{Principal, encode_one};
use pocket_ic::PocketIc;

// 2T cycles
const INIT_CYCLES: u128 = 2_000_000_000_000;

#[test]
fn test_counter_canister() {
// Create new PocketIC instance
let pic = PocketIc::new();

// Create a canister and charge it with 2T cycles.
let canister_id = pic.create_canister();
pic.add_cycles(canister_id, INIT_CYCLES);

// Install the counter canister wasm file on the canister.
let counter_wasm = todo!();
pic.install_canister(canister_id, counter_wasm, vec![], None);

// Make some calls to the canister.
let reply = call_counter_can(&pic, canister_id, "read");
assert_eq!(reply, vec![0, 0, 0, 0]);
let reply = call_counter_can(&pic, canister_id, "write");
assert_eq!(reply, vec![1, 0, 0, 0]);
let reply = call_counter_can(&pic, canister_id, "write");
assert_eq!(reply, vec![2, 0, 0, 0]);
let reply = call_counter_can(&pic, canister_id, "read");
assert_eq!(reply, vec![2, 0, 0, 0]);
}

fn call_counter_can(pic: &PocketIc, canister_id: Principal, method: &str) -> Vec<u8> {
pic.update_call(
canister_id,
Principal::anonymous(),
method,
encode_one(()).unwrap(),
)
.expect("Failed to call counter canister")
}

View additional Rust examples.

Multi-subnet testing & different subnet types

Multi-subnet testing allows for simulating multiple subnets, including subnets of different types. Possible types of subnets include:

  • Generic system subnets.

  • Generic application subnets.

  • Named subnets with canister ID ranges like on the mainnet, such as the NNS, SNS, II, Bitcoin, and Fiduciary subnets.

This example uses an NNS subnet and two application subnets.

multi_subnet.rs
use candid::{Principal, encode_one};
use pocket_ic::PocketIc;

let pic = PocketIcBuilder::new()

// Create an IC instance with an NNS subnet and two application subnets
    .with_nns_subnet()
    .with_application_subnet()
    .with_application_subnet()
    .build();

// Target the NNS subnet to create a canister
let nns_sub = pic.topology().get_nns_subnet().unwrap();
let nns_can_id = pic.create_canister_on_subnet(..., nns_sub);

// Test one of the application subnets and install a canister
let app_sub_2 = pic.topology().get_app_subnets()[1];
let app_can_id = pic.create_canister_on_subnet(..., app_sub_2);
pic.install_canister(app_can_id, ...);

// Create a canister with a specific `canister_id` on a named subnet, in this example the NNS subnet
let ledger_canister_id = Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap();
pic.create_canister_with_id(..., ledger_canister_id).unwrap();
pic.install_canister(ledger_canister_id, ...);

View a larger, more complex example.

Resources