"Counter" Tutorial
Let's create a simple project and test it using the ic-test
.
Create a "Hello, World!" canister:
dfx new hello-ic-test --type rust --no-frontend
Compile the project:
dfx start --clean --background
dfx canister create --all
dfx build
Generate test bindings
If there are uncommitted changes, either commit them before generating or use the --force
flag:
ic-test new tests --force
This creates a tests package with:
- Canister API bindings in
tests/src/bindings
- Test environment setup logic in
test_setup.rs
- A test template in
tests.rs
Example test
Edit tests.rs
:
use ic_test::IcpTest;
use crate::test_setup;
#[tokio::test]
async fn test_greet() {
let test_setup::Env {
icp_test,
hello_ic_test_backend,
} = test_setup::setup(IcpTest::new().await).await;
let result = hello_ic_test_backend
.greet("ic-test".to_string())
.call()
.await;
assert_eq!(result, "Hello, ic-test!");
}
Run tests:
cargo test
Adding a counter
Update the canister backend:
#![allow(unused)] fn main() { use std::cell::RefCell; #[ic_cdk::query] fn greet(name: String) -> String { format!("Hello, {}!", name) } #[derive(Clone, Default)] struct CounterState { value: u64, increment: u64, } thread_local! { static STATE: RefCell<CounterState> = RefCell::new(CounterState::default()); } #[ic_cdk::init] fn init(init_value: u64, increment: u64) { STATE.with(|state| { *state.borrow_mut() = CounterState { value: init_value, increment, }; }); } #[ic_cdk::update] fn increment_counter() { STATE.with(|state| { let mut s = state.borrow_mut(); s.value += s.increment; }); } #[ic_cdk::query] fn get_counter() -> u64 { STATE.with(|state| state.borrow().value) } }
Update Candid file hello-ic-test-backend.did
:
service : (nat64, nat64) -> {
"greet": (text) -> (text) query;
"get_counter": () -> (nat64) query;
"increment_counter": () -> ();
}
Set initialization arguments in dfx.json
:
{
"canisters": {
"hello-ic-test-backend": {
"candid": "src/hello-ic-test-backend/hello-ic-test-backend.did",
"package": "hello-ic-test-backend",
"type": "rust",
"init_arg": "(50, 73)"
}
},
"defaults": {
"build": {
"args": "",
"packtool": ""
}
},
"output_env_file": ".env",
"version": 1
}
Regenerate the bindings:
dfx build
ic-test
The ic-test
will enter interactive mode and prompt user to allow overwriting the test_setup.rs
file. Upon confirmation the the test_setup.rs
is regenerated with the initialization parameters:
//...
let hello_ic_test_backend = hello_ic_test_backend::deploy(&icp_user, 50, 73)
.call()
.await;
// ...
New test
Add a new test in tests.rs
:
#![allow(unused)] fn main() { #[tokio::test] async fn test_counter() { let test_setup::Env { icp_test, hello_ic_test_backend, } = test_setup::setup(IcpTest::new().await).await; let result = hello_ic_test_backend.get_counter().call().await; assert_eq!(result, 50u64); hello_ic_test_backend.increment_counter().call().await; let result = hello_ic_test_backend.get_counter().call().await; assert_eq!(result, 123u64); // 50 + 73 } }
Run tests:
cargo test