Introducing multitest

Let me introduce the multitest - library for creating tests for smart contracts in Rust.

The core idea of multitest is abstracting an entity of contract and simulating the blockchain environment for testing purposes. The purpose of this is to be able to test communication between smart contracts. It does its job well, but it is also an excellent tool for testing single-contract scenarios.

Update dependencies

First, we need to add sylvia with mt feature enabled to our dev-dependencies.

[package]
name = "contract"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
cosmwasm-std = { version = "2.0.4", features = ["staking"] }
sylvia = "1.1.0"
schemars = "0.8.16"
cosmwasm-schema = "2.0.4"
serde = "1.0.180"
cw-storage-plus = "2.0.0"

[dev-dependencies]
sylvia = { version = "1.1.0", features = ["mt"] }
cw-multi-test = { version = "2.1.0", features = ["staking"] }

Creating a module for tests

Now we will create a new module, multitest. Let's first add it to the src/lib.rs

pub mod contract;
#[cfg(test)]
pub mod multitest;
pub mod responses;

As this module is purely for testing purposes, we prefix it with #[cfg(test)].

Now create src/multitest.rs.

use sylvia::cw_multi_test::IntoAddr;
use sylvia::multitest::App;

use crate::contract::sv::mt::{CodeId, CounterContractProxy};

#[test]
fn instantiate() {
    let app = App::default();
    let code_id = CodeId::store_code(&app);

    let owner = "owner".into_addr();

    let contract = code_id.instantiate(42).call(&owner).unwrap();

    let count = contract.count().unwrap().count;
    assert_eq!(count, 42);
}

Sylvia generates a lot of helpers for us to make testing as easy as possible. To simulate blockchain, we create sylvia::multitest::App. Then we will use it to store the code id of our contract on the blockchain using sylvia generated CodeId.

Code id identifies our contract on the blockchain and allows us to instantiate the contract on it. We do that using CodeId::instantiate method. It returns the InstantiateProxy type, allowing us to set some contract parameters on a blockchain. You can inspect methods like with_label(..), with_funds(..) or with_admins(..). Once all parameters are set you use call passing caller to it as an only argument. This will return Result<ContractProxy, ..>. Let's unwrap it as it is a testing environment and we expect it to work correctly.

Now that we have the proxy type we have to import CounterContractProxy to the scope. It's a trait defining methods for our contract. With that setup we can call our count method on it. It will generate appropriate QueryMsg variant underneath and send to blockchain so that we don't have to do it ourselves and have business logic transparently shown in the test. We unwrap and extract the only field out of it and that's all.

Next step

We tested our contract on a simulated environment. Now let's add some fluidity to it and introduce execute messages which will alter the state of our contract.