Best practices for integration tests of Rust canisters

Are there any best practices / guides available for integration testing of Rust canisters or canisters in general?
I’ve found this guide that was written a while back. Are there any other useful resources available?

1 Like

If you are ok skipping the frontend I like to use the state machine for anything where unit tests are not good enough

1 Like

Hello,

As each integration test has to run in a clean environment, how would you re-init each canister state between each test?
Using -Mode reinstall from dfx currently seems to be the only solution?
Do you have any other solutions/ideas?

If you use the StateMachine it’s a lot faster. But yes, reinstalling is the only way to get a clean state on a replica

I’ve never used it, do you have any information or documentation on it?

I’m waiting for one of our repos to be open-sourced so I have a nice example to show. Probably only a few days away. Setting up the binary is described here. And here’s a few snippets to get you started:

Creating a StateMachine:

fn new_state_machine() -> StateMachine {
    let mut state_machine_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .to_path_buf();

    state_machine_path.push("ic-test-state-machine");

    if !state_machine_path.exists() {

        panic!("state machine binary does not exist");
    }
    StateMachine::new(state_machine_path.to_str().unwrap(), false)
}

Then you can write utility functions like this:

pub fn transfer(
    env: &StateMachine,
    ledger_id: Principal,
    from: Account,
    args: TransferArgs,
) -> Result<Nat, TransferError> {
    let arg = Encode!(&args).unwrap();
    if let WasmResult::Reply(res) = env
        .update_call(ledger_id, from.owner, "icrc1_transfer", arg)
        .unwrap()
    {
        Decode!(&res, Result<candid::Nat, TransferError>).unwrap()
    } else {
        panic!("transfer rejected")
    }
}

And with a bunch of those your tests can look like this:

#[test]
fn test_deposit_flow() {
    let env = &new_state_machine();
    let ledger_id = install_ledger(env);
    let depositor_id = install_depositor(env, ledger_id);
    let user = Account {
        owner: Principal::from_slice(&[0]),
        subaccount: None,
    };

    // Check that the total supply is 0
    assert_eq!(total_supply(env, ledger_id), 0u128);

    // Check that the user doesn't have any tokens before the first deposit.
    assert_eq!(balance_of(env, ledger_id, user), 0u128);

    // Make the first deposit to the user and check the result.
    let deposit_res = deposit(env, depositor_id, user, 1_000_000_000);
    assert_eq!(deposit_res.txid, Nat::from(0));
    assert_eq!(deposit_res.balance, Nat::from(1_000_000_000));
...
5 Likes

You can also try to use lightic, nodejs package. It simulates replica in nodejs context, so you can deploy and test multi canister projects (works with any canister compiled to wasm, so both rust and motko).

3 Likes