Skip to content

docs: PocketIC testing guide#67

Merged
marc0olo merged 3 commits into
mainfrom
docs/guides-testing-pocket-ic
Apr 16, 2026
Merged

docs: PocketIC testing guide#67
marc0olo merged 3 commits into
mainfrom
docs/guides-testing-pocket-ic

Conversation

@marc0olo
Copy link
Copy Markdown
Member

Summary

  • Explains what PocketIC is: in-process IC replica that strips consensus/networking for fast deterministic tests
  • Client library table: Rust (pocket-ic), JavaScript/TypeScript (Pic JS), Python
  • Rust setup: dev dependency, basic test pattern (create canister, install WASM, query/update calls), helper struct pattern for multi-test setups
  • Canister lifecycle in tests: install, upgrade, stop, start
  • Time travel: advance_time + tick for timer testing
  • Multi-subnet testing with PocketIcBuilder (NNS + application subnets)
  • JavaScript/TypeScript via Pic JS: install, basic Jest test with PocketIc.create(), teardown, time advancement
  • CI caching strategy for the PocketIC server binary
  • When to use PocketIC vs containerized networks

Sync recommendation

informed by dfinity/portaldocs/building-apps/test/pocket-ic.mdx; informed by dfinity/examplesrust/unit_testable_rust_canister, rust/guards

@marc0olo marc0olo force-pushed the docs/guides-testing-pocket-ic branch from e53cacc to f10264f Compare April 16, 2026 13:37
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@marc0olo
Copy link
Copy Markdown
Member Author

Updated Pic JS section:

  • Replaced deprecated @hadronous/pic package with current @dfinity/pic (@dfinity/pic is the official package; @hadronous/pic was a community prototype)
  • Updated install command
  • Rewrote basic test example to use PocketIcServer.start() / PocketIc.create(picServer.getUrl()) lifecycle (current API)
  • Updated timer test with correct PocketIcServer lifecycle
  • Updated POCKET_IC_URLPOCKET_IC_SERVER_PATH env var reference
  • Added icp-js-sdk-docs to <!-- Upstream: --> comment

@marc0olo
Copy link
Copy Markdown
Member Author

Review: PocketIC

Must fix

  • Rust API inconsistency — WasmResult vs Vec<u8> mismatch: The page contains two Rust code patterns that assume different versions of the pocket-ic crate. The basic test (lines ~76–108) calls .expect("query failed") and then passes the result directly to decode_one(&result), treating it as Vec<u8>. The helper struct example (lines ~152–176) correctly pattern-matches WasmResult::Reply(bytes) before decoding. These two patterns are mutually exclusive — pocket-ic v4.x returns Result<WasmResult, UserError>, while v9.x returns Result<Vec<u8>, UserError> directly. A developer copying both examples into the same project will get a compile error. The page needs to pick one version and be consistent throughout. Based on the latest v9 API (confirmed in .sources/examples/rust/unit_testable_rust_canister/Cargo.lock), the basic test style is correct for v9+ and the WasmResult pattern is correct for v4. Recommend standardizing on v9+ and removing WasmResult from the helper struct example, or adding a version callout. The Cargo.toml example uses pocket-ic = "*" which makes the version ambiguity worse.

  • JS setupCanister missing required idlFactory: The JS basic test example calls pic.setupCanister({ wasm: WASM_PATH }) but SetupCanisterOptions.idlFactory is a required (non-optional) field per the API docs in .sources/icp-js-sdk-docs/public/pic-js/latest.zip (api/interfaces/SetupCanisterOptions.md). Without idlFactory, this will fail TypeScript compilation and the returned actor will be untyped. The actor.increment() and actor.get_count() calls on the returned fixture are also uncallable without type information. Fix: add idlFactory to the setupCanister call, or switch to a lower-level pattern using pic.createCanister() / pic.installCode() / pic.createActor() which doesn't require idlFactory.

  • Wildcard version pocket-ic = "*" in Cargo.toml example: The dev-dependency snippet uses pocket-ic = "*". This is unusable in practice — Cargo requires a concrete version specifier in Cargo.toml. A developer copying this will get a semver mismatch on the API depending on what resolves. Use a pinned version (e.g., pocket-ic = "9") that matches the API style used in the code examples.

Suggestions

  • POCKET_IC_SERVER_PATH env var unverified for JS: The statement "Set POCKET_IC_SERVER_PATH to point to a pre-installed binary" for Pic JS is not documented in the JS SDK API reference (StartServerOptions only has showCanisterLogs and showRuntimeLogs). This may be an undocumented environment variable that the server reads directly (outside the options object), but it cannot be verified from our sources. Add a <!-- Needs human verification: POCKET_IC_SERVER_PATH env var name for @dfinity/pic — not in API docs --> comment, or verify against the pic-js source directly.

  • Canister lifecycle example references undefined WASM_V1 / WASM_V2: The test_upgrade snippet at lines ~194–210 references WASM_V1 and WASM_V2 without showing how they are defined. Readers who copy this won't know what those constants represent. Either define them in the snippet (similar to how CANISTER_WASM is defined with include_bytes!) or add a brief comment like // defined like CANISTER_WASM above.

  • Missing PocketIcBuilder import in multi-subnet example: The multi-subnet test imports PocketIcBuilder but the use statement is use pocket_ic::{PocketIc, PocketIcBuilder}; while the initial test examples only import use pocket_ic::PocketIc. The multi-subnet example should explicitly show the import includes PocketIcBuilder since it's used in PocketIcBuilder::new(). The current snippet does show the correct import on its own, but readers may be confused if they try to add it incrementally from the basic example.

  • JS advance-time example lacks tearDown/stop lifecycle management: The timer test in the JS section (lines ~337–352) creates picServer and pic inside a single test (it()), which is fine, but it runs setup inline rather than in beforeAll/beforeEach hooks. This creates inconsistency with the Jest example above it. Suggest adding a brief note that this flatter pattern works in simple tests but the beforeAll/afterAll pattern shown earlier is preferred for test suites.

  • icp network start vs icp-cli local development network: The intro says "Unlike the full local network started by icp network start". This is technically accurate but the sidebar context would benefit from a brief cross-reference to the strategies.md page earlier — it's already linked in the "When to use" callout, so this is minor.

  • Python column in client library table: The table lists Python but the rest of the page has no Python examples. The portal source does include Python examples. Consider either adding a brief Python section (even one code snippet) or removing Python from the table with a note pointing to the upstream Python library, so the table doesn't imply coverage that isn't there.

Verified

  • All three internal links resolve: strategies.md, ../governance/testing.md, ../../languages/rust/testing.md
  • No banned patterns found: no dfx, no mo:base, no internetcomputer.org/docs/ links, no @hadronous/pic
  • JS package name @dfinity/pic is correct (not the deprecated @hadronous/pic)
  • PocketIcServer.start() and PocketIc.create(picServer.getUrl()) pattern verified against .sources/icp-js-sdk-docs/public/pic-js/latest.zip (api/classes/PocketIcServer.md, api/classes/PocketIc.md)
  • pic.advanceTime(duration) takes milliseconds — confirmed against JS API docs
  • pic.tearDown() and picServer.stop() exist with correct signatures
  • pic.tick() exists with correct signature
  • create_canister_on_subnet(None, None, subnet) signature confirmed against .sources/examples/rust/parallel_calls/
  • topology().get_app_subnets() usage confirmed against .sources/examples/rust/parallel_calls/
  • upgrade_canister, stop_canister, start_canister method names are standard pocket-ic API
  • encode_one, decode_one, Encode!, Decode! are correct candid import/usage patterns
  • PocketIcBuilder::new().with_nns_subnet().with_application_subnet().build() pattern confirmed against portal source material
  • POCKET_IC_BIN env var for Rust confirmed against portal source (docs/building-apps/test/pocket-ic.mdx, line 38)
  • External links verified: crates.io/crates/pocket-ic, npmjs.com/package/@dfinity/pic, pypi.org/project/pocket-ic/ all use correct package names matching official packages
  • icp network start CLI command verified against .sources/icp-cli/docs/reference/cli.md
  • https://cli.internetcomputer.org/ for icp-cli external link is correct per content-authoring linking rules
  • https://js.icp.build/pic-js for Pic JS docs is an allowed host per project config
  • Frontmatter is complete with title, description, and sidebar.order
  • <!-- Upstream: --> comment present at end of file with all referenced sources
  • No <!-- TODO: verify output --> or <!-- Needs human verification: --> flags left unresolved (except the POCKET_IC_SERVER_PATH one noted above)
  • Content follows Diataxis guide format (task-oriented, CLI where relevant)
  • Page ends with ## Next steps section as required
  • .md file extension used (correct, no interactive components needed)
  • wasm: WASM_PATH field name in setupCanister options is correct per SetupCanisterOptions interface (the wasm field, not wasmPath)

@marc0olo
Copy link
Copy Markdown
Member Author

Feedback addressed (PR #67 — PocketIC page)

Must-fix items applied

1. Rust API inconsistency — WasmResult vs Vec mismatch (standardized on v9+)

The helper struct example used WasmResult::Reply(bytes) (pocket-ic v4 style), while the basic test correctly used decode_one(&result) treating the result as Vec<u8> (v9 style). Verified the v9 API against .sources/examples/rust/unit_testable_rust_canister/Cargo.lock (pocket-ic 9.0.2) and integration_tests.rs. Updated the helper struct to use the v9 pattern: decode_one(&bytes).unwrap() on the Vec<u8> returned directly. Removed WasmResult import.

2. JS setupCanister missing required idlFactory

Verified against .sources/icp-js-sdk-docs/public/pic-js/latest.zip (api/interfaces/SetupCanisterOptions.md): idlFactory is a required field. Added idlFactory and _SERVICE imports and added idlFactory to the setupCanister call. Updated prose to explain declarations generation.

3. Wildcard version pocket-ic = "*" replaced with pocket-ic = "9"

Pinned to major version 9, matching the API used throughout the code examples.

Suggestions applied

4. POCKET_IC_SERVER_PATH env var — added verification comment

The env var is not documented in StartServerOptions (verified against API docs). Replaced the claim with a <\!-- Needs human verification --> comment.

5. WASM_V1/WASM_V2 undefined — added clarifying comment

Added a comment above the lifecycle example showing how WASM_V1/WASM_V2 would be defined.

6. JS timer example — added note about pattern preference

Added a note before the timer snippet recommending beforeAll/afterAll for test suites.

Suggestions skipped

  • Multi-subnet import: already correct in the example
  • Python table entry: kept as-is; structural change requiring human sign-off
  • icp network start cross-ref: already handled by existing "When to use" callout linking to strategies.md

Build status

Build fails on docs/guides/backends/https-outcalls.mdx (missing submodule in worktree — pre-existing, unrelated to this page). The pocket-ic.md page builds without errors.

@marc0olo marc0olo merged commit 5bac9dd into main Apr 16, 2026
1 check passed
@marc0olo marc0olo deleted the docs/guides-testing-pocket-ic branch April 16, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant