Skip to main content

pocket_ic_harness/
lib.rs

1//! A test harness for Internet Computer canisters using PocketIC.
2//!
3//! This crate provides reusable utilities for integration testing IC canisters:
4//!
5//! - [`Canister`] trait — define your canisters and their WASM paths
6//! - [`CanisterSetup`] trait — define how canisters are installed before each test
7//! - [`PocketIcTestEnv`] — generic test environment with canister installation and registry
8//! - [`PocketIcClient`] — typed query/update calls with Candid encoding
9//! - [`init_new_agent`] — create IC agents against PocketIC endpoints
10//! - [`test`] — proc-macro attribute for automatic setup/teardown
11//!
12//! # Quick Start
13//!
14//! Define your canisters and setup:
15//!
16//! ```rust,ignore
17//! use std::path::Path;
18//! use candid::Encode;
19//! use pocket_ic_harness::{Canister, CanisterSetup, PocketIcTestEnv};
20//!
21//! #[derive(Debug, Clone, Hash, PartialEq, Eq)]
22//! enum MyCanister {
23//!     Backend,
24//! }
25//!
26//! impl Canister for MyCanister {
27//!     fn as_path(&self) -> &Path {
28//!         match self {
29//!             MyCanister::Backend => Path::new("path/to/backend.wasm.gz"),
30//!         }
31//!     }
32//!
33//!     fn all_canisters() -> &'static [Self] {
34//!         &[Self::Backend]
35//!     }
36//! }
37//!
38//! struct MySetup;
39//!
40//! impl CanisterSetup for MySetup {
41//!     type Canister = MyCanister;
42//!
43//!     async fn setup(env: &mut PocketIcTestEnv<Self>) {
44//!         let init_arg = Encode!(&()).unwrap();
45//!         env.install_canister(MyCanister::Backend, init_arg).await;
46//!     }
47//! }
48//! ```
49//!
50//! Write tests with the proc-macro — canisters are already installed:
51//!
52//! ```rust,ignore
53//! #[pocket_ic_harness::test]
54//! async fn test_my_canister(ctx: PocketIcTestEnv<MySetup>) {
55//!     let canister_id = ctx.canister_id(&MyCanister::Backend);
56//!     // test your canister...
57//! }
58//! ```
59
60mod actor;
61mod agent;
62mod client;
63mod pocket_ic;
64
65use std::hash::Hash;
66use std::path::Path;
67
68pub use pocket_ic_harness_macro::test;
69
70pub use self::actor::{admin, alice, bob};
71pub use self::agent::init_new_agent;
72pub use self::client::PocketIcClient;
73pub use self::pocket_ic::PocketIcTestEnv;
74
75/// Trait for identifying a canister and locating its WASM binary.
76///
77/// Implement this on an enum representing your project's canisters.
78///
79/// # Example
80///
81/// ```rust
82/// use std::path::Path;
83/// use pocket_ic_harness::Canister;
84///
85/// #[derive(Debug, Clone, Hash, PartialEq, Eq)]
86/// enum MyCanister {
87///     Backend,
88///     Frontend,
89/// }
90///
91/// impl Canister for MyCanister {
92///     fn as_path(&self) -> &Path {
93///         match self {
94///             MyCanister::Backend => Path::new("artifacts/backend.wasm.gz"),
95///             MyCanister::Frontend => Path::new("artifacts/frontend.wasm.gz"),
96///         }
97///     }
98///
99///     fn all_canisters() -> &'static [Self] {
100///         &[Self::Backend, Self::Frontend]
101///     }
102/// }
103/// ```
104pub trait Canister: Hash + Eq + Sized + Clone + 'static {
105    /// Returns the path to the WASM binary for this canister.
106    ///
107    /// The path is used as-is when loading the WASM file.
108    fn as_path(&self) -> &Path;
109
110    /// Returns a static list of all canisters of this type.
111    ///
112    /// The test environment creates all canisters before installing any of them,
113    /// so that init arguments can reference other canisters' principals.
114    fn all_canisters() -> &'static [Self];
115}
116
117/// Trait for defining canister installation and configuration.
118///
119/// Implement this to specify which canisters to install and how to configure
120/// them during test environment initialization. The setup is called
121/// automatically by [`PocketIcTestEnv::init`].
122///
123/// # Example
124///
125/// ```rust,ignore
126/// use candid::Encode;
127/// use pocket_ic_harness::{CanisterSetup, PocketIcTestEnv};
128///
129/// struct MySetup;
130///
131/// impl CanisterSetup for MySetup {
132///     type Canister = MyCanister;
133///
134///     async fn setup(env: &mut PocketIcTestEnv<Self>) {
135///         let init_arg = Encode!(&()).unwrap();
136///         env.install_canister(MyCanister::Backend, init_arg).await;
137///     }
138/// }
139/// ```
140pub trait CanisterSetup {
141    /// The canister type used by this setup.
142    type Canister: Canister;
143
144    /// Install and configure canisters in the test environment.
145    fn setup(env: &mut PocketIcTestEnv<Self>) -> impl Future<Output = ()>
146    where
147        Self: Sized;
148}