pocket-ic 1.0.0

PocketIC: A Canister Smart Contract Testing Platform
Documentation

PocketIC: A Canister Testing Platform

PocketIC is the local canister smart contract testing platform for the Internet Computer.

It consists of the PocketIC server, which can run many independent IC instances, and a client library (this crate), which provides an interface to your IC instances.

With PocketIC, testing canisters is as simple as calling rust functions. Here is a minimal example:

use candid;
use pocket_ic;

 #[test]
 fn test_counter_canister() {
    let pic = PocketIc::new();
    // Create an empty canister as the anonymous principal.
    let canister_id = pic.create_canister(None);
    pic.add_cycles(canister_id, 1_000_000_000_000_000);
    let wasm_bytes = load_counter_wasm(...);
    pic.install_canister(canister_id, wasm_bytes, vec![], None);
    // 'inc' is a counter canister method.
    call_counter_canister(&pic, canister_id, "inc");
    // Check if it had the desired effect.
    let reply = call_counter_canister(&pic, canister_id, "read");
    assert_eq!(reply, WasmResult::Reply(vec![0, 0, 0, 1]));
 }

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

Examples

For a simple but complete example with the counter canister, see here.

For larger test suites with more complex test setups, consider the Internet Identity and the OpenChat integration test suites.

Getting Started

  • Download the PocketIC binary for Linux or MacOS and make it executable.
  • Write the binary path into the environment variable POCKET_IC_BIN. You can also prepend POCKET_IC_BIN=... to your cargo test invocation.
  • Add the pocket-ic crate to your Cargo.toml, or install it with cargo install pocket-ic.
  • Depending on your use case, you may also need the candid crate.

Why PocketIC?

Canister developers have several options to test their software, but there are tradeoffs:

  • Install and test on the mainnet: The 'real' experience, but you pay with real cycles.
  • The replica provided by DFX: You get the complete stack of a single IC node. But therefore, you get no cross- or multisubnet functionality, and likely never will. Replica is quite heavyweight too, because the nonessential components are not abstracted away. Furthermore, testing with replica is not deterministic.

Enter PocketIC:

  • Deterministic: Synchronous control over the IC's execution environment
  • Lightweight: Mocks the consensus and networking layers
  • Versatile: Runs as a service on your test system, and accepts HTTP/JSON. This enables:
    • Concurrent and independent IC instances by default - sharing is possible
    • Multi-language support: Anyone can write an integration library against the PocketIC REST-API in any language
  • [Will support saving and loading checkpoints]
  • [Will support multi-subnet IC instances]

How to use this library

You create an empty IC instance by instantiating PocketIc:

let pic = PocketIc::new();

This constructor will discover an already running instance of the PocketIC Server or launch a fresh one. It then requests a fresh instance and serves as a unique reference to that instance. When the value is dropped, the instance on the PocketIC Server will be deleted.

This design promotes test isolation, and we recommend to use one PocketIc instance per test. However, it is still possible to share a PocketIc instance between tests, but you do so at your own risk concerning 1) determinism and 2) performance (concurrent tests may block each other).

Using a value of the PocketIc struct, you interact with the IC itself, e.g. via:

// IC interface excerpt
fn root_key(&self)  
fn set_time(&self, t: Time) 
fn create_canister(&self, sender: Principal) -> CanisterId
fn install_canister(&self, c: CanisterId, wasm: Vec<u8>, ...)  
...

and you interact with the canisters you have created:

// Canister interface excerpt
fn add_cycles(&self, c: CanisterId, cy: Cycles)
fn update_call(&self, c: CanisterId, sender: Principal, method: String, ...) -> Result<...>
fn upgrade_canister(&self, c: CanisterId, wasm: Vec<u8>, ...)
...

You can also use your canister's candid interface like this:

let MyResult{my_value} = call_candid(&pic, canister_id, "my_candid_method", (arg1, arg2));

Note that you have to provide your method arguments (arg1, arg2) as a tuple, because it will be encoded to candid automatically. Similarly for the return value, call_candid tries to decode the candid-encoded reply from the canister to your rust struct. For general info on candid, see here and for candid in rust here.

See the examples for more.

Contributing

Would you like to write canister tests in a different language? Using PocketIC, you can! The PocketIC binary has a JSON/REST interface, against which you may implement any user-facing library in any language. See for example the Python library.

If you decide to contribute, we encourage you to announce it on the Internet Computer Developer Forum.