AppBuilder
AppBuilder
is an implementation of the Builder Pattern that provides a
flexible and modular way to construct the App
blockchain simulator. It allows smart
contract developers to configure various components of the blockchain simulator (e.g., Bank,
Staking, Gov, IBC) individually through dedicated with_*
methods. Each method modifies and returns
a new instance of the builder, enabling method chaining for a fluent interface. The
build
method finalizes the construction process, ensuring that all components are
correctly initialized and integrated.
The following sections detail all builder functions, providing specific usage examples.
default
The simplest way to create a chain using AppBuilder
is by calling the
default
method. Since AppBuilder
follows the principles of the
builder pattern, you need to finalize the building process by calling the build
method with a chain initialization callback function. When no specific chain initialization is
required you can just use provided no_init
callback. In the following code example,
the chain is created with default settings as described in
Features summary.
use cw_multi_test::{no_init, AppBuilder};
let app = AppBuilder::default().build(no_init);
let sender_addr = app.api().addr_make("sender");
assert!(sender_addr.as_str().starts_with("cosmwasm1"));
new
The constructor new
is an equivalent of the default
method in
AppBuilder
. The example below creates a chain with default settings as
described in Features summary.
use cw_multi_test::{no_init, AppBuilder};
let app = AppBuilder::new().build(no_init);
let sender_addr = app.api().addr_make("sender");
assert!(sender_addr.as_str().starts_with("cosmwasm1"));
new_custom
(WIP)
with_api
The default Api
trait implementation used in AppBuilder
is
cosmwasm_std::testing::MockApi
provided by CosmWasm library. Besides other
functionalities described in detail in the API chapter, the MockApi
provides a function addr_make
for generating user addresses in Bech32
format with the cosmwasm
prefix. An example usage is shown below.
use cw_multi_test::{no_init, AppBuilder};
// Create the chain with default Api implementation.
let app = AppBuilder::default().build(no_init);
// Create the address using default Api.
let sender_addr = app.api().addr_make("sender");
// Default Api generates Bech32 addresses with prefix 'cosmwasm'.
assert_eq!(
"cosmwasm1pgm8hyk0pvphmlvfjc8wsvk4daluz5tgrw6pu5mfpemk74uxnx9qlm3aqg",
sender_addr.as_str()
);
If you need to test contracts on your own chain that uses a specific Bech32 prefixes, you can easily
customize the default MockApi
behavior using the
AppBuilder::with_api
method. An example of using osmo
prefixes is shown in
the following code snippet.
use cosmwasm_std::testing::MockApi;
use cw_multi_test::{no_init, AppBuilder};
// Create the chain with customized default Api implementation.
let app = AppBuilder::default()
.with_api(MockApi::default().with_prefix("osmo"))
.build(no_init);
// Create the address using customized Api.
let sender_addr = app.api().addr_make("sender");
// This customized Api generates Bech32 addresses with prefix 'osmo'.
assert_eq!(
"osmo1pgm8hyk0pvphmlvfjc8wsvk4daluz5tgrw6pu5mfpemk74uxnx9qcrt3u2",
sender_addr.as_str()
);
MultiTest
provides two additional implementations of the Api
trait:
MockApiBech32
and MockApiBech32m
. You can use
them in your tests by providing their instances to AppBuilder::with_api
method.
An example of using MockApiBech32
with custom prefix:
use cw_multi_test::MockApiBech32;
use cw_multi_test::{no_init, AppBuilder};
// Create the chain with Bech32 Api implementation.
let app = AppBuilder::default()
.with_api(MockApiBech32::new("juno"))
.build(no_init);
// Create the address using Bech32 Api.
let sender_addr = app.api().addr_make("sender");
// This Api generates Bech32 addresses with prefix 'juno'.
assert_eq!(
"juno1pgm8hyk0pvphmlvfjc8wsvk4daluz5tgrw6pu5mfpemk74uxnx9qwm56ug",
sender_addr.as_str()
);
An example of using MockApiBech32m
with custom prefix:
use cw_multi_test::MockApiBech32m;
use cw_multi_test::{no_init, AppBuilder};
// Create the chain with Bech32m Api implementation.
let app = AppBuilder::default()
.with_api(MockApiBech32m::new("juno"))
.build(no_init);
// Create the address using Bech32m Api.
let sender_addr = app.api().addr_make("sender");
// This Api generates Bech32m addresses with prefix 'juno'.
assert_eq!(
"juno1pgm8hyk0pvphmlvfjc8wsvk4daluz5tgrw6pu5mfpemk74uxnx9qm8yke2",
sender_addr.as_str()
);
with_bank
(WIP)
with_block
While the default block configuration is sufficient for most test cases, you can initialize the
chain with a custom BlockInfo
using the with_block
method provided by AppBuilder
.
The following example demonstrates this use case in detail.
use cosmwasm_std::{BlockInfo, Timestamp};
use cw_multi_test::{no_init, AppBuilder};
// create the chain builder
let builder = AppBuilder::default();
// prepare the custom block
let block = BlockInfo {
height: 1,
time: Timestamp::from_seconds(1723627489),
chain_id: "starship-testnet".to_string(),
};
// build the chain initialized with the custom block
let app = builder.with_block(block).build(no_init);
// get the current block properties
let block = app.block_info();
// now the block height is 21
assert_eq!(1, block.height);
// now the block timestamp is Wed Aug 14 2024 09:24:49 GMT+0000
assert_eq!(1723627489, block.time.seconds());
// now the chain identifier is "starship-testnet"
assert_eq!("starship-testnet", block.chain_id);
The AppBuilder
is initialized with default settings in line 5. A custom block
is created in lines 8-12 and passed to the with_block
method in line 15.
Since this is the only customization in this example, the blockchain construction is finalized in
the same line by calling the build
method.
The current block metadata is retrieved in line 18, followed by value checks:
- line 21: the block height is now
1
, - line 24: the block time is set to the Unix timestamp
1723627489
, representingWednesday, August 14, 2024, 09:24:49 GMT
, - line 27: the chain identifier is now
"starship-testnet"
.
The with_block
method of AppBuilder
can be combined
with any other with_*
methods to configure a custom starting block.
with_custom
(WIP)
with_distribution
(WIP)
with_gov
The with_gov
function allows you to customize the governance module of your
test blockchain environment. This function enables you to override the default governance module
with a custom implementation that suits your specific testing needs. Currently, MultiTest
provides two minimal implementations of the governance module that do not attempt to replicate real
blockchain behavior: GovAcceptingModule
and
GovFailingModule
.
To use a built-in governance module that accepts all messages, initialize the chain like shown below (line 4):
use cw_multi_test::{no_init, AppBuilder, GovAcceptingModule};
let app = AppBuilder::default()
.with_gov(GovAcceptingModule::new())
.build(no_init);
When processing governance messages in your tests should always fail, initialize the chain like in the following code snippet (line 4):
use cw_multi_test::{no_init, AppBuilder, GovFailingModule};
let app = AppBuilder::default()
.with_gov(GovFailingModule::new())
.build(no_init);
Note that GovFailingModule
is the default one in App
.
You can find more usage examples of the built-in governance modules in the Governance chapter.
with_ibc
(WIP)
with_staking
(WIP)
with_stargate
(WIP)
with_storage
By default, MultiTest
uses MockStorage
, which is provided by the
CosmWasm library. MockStorage
is an in-memory storage that does not persist
data beyond the test execution. Any values stored in it during testing are lost once the test
completes.
To use the default storage, simply initialize a chain with the default settings, as shown in the
following code snippet. After initialization, App
provides two methods to access the
storage: storage
and storage_mut
. The former is used for
reading the storage, while the latter allows both reading and writing.
use cosmwasm_std::Storage;
use cw_multi_test::{no_init, AppBuilder};
let mut app = AppBuilder::default().build(no_init);
let key = b"key";
let value = b"value";
app.storage_mut().set(key, value);
assert_eq!(value, app.storage().get(key).unwrap().as_slice());
You can also explicitly provide a MockStorage
instance to the
with_storage
method of AppBuilder
. The example below
achieves the same result as the previous one.
use cosmwasm_std::testing::MockStorage;
use cosmwasm_std::Storage;
use cw_multi_test::{no_init, AppBuilder};
let mut app = AppBuilder::default()
.with_storage(MockStorage::new())
.build(no_init);
let key = b"key";
let value = b"value";
app.storage_mut().set(key, value);
assert_eq!(value, app.storage().get(key).unwrap().as_slice());
Initializing a chain with a MockStorage
instance using the
with_storage
method of AppBuilder
allows for
sophisticated storage initialization before the chain starts. A simplified example is shown below.
In lines 8-9 the storage is created and initialized, then passed to with_storage
method in
line 11. The initialization code in line 9 can of course be more complex in your test case.
use cosmwasm_std::testing::MockStorage;
use cosmwasm_std::Storage;
use cw_multi_test::{no_init, AppBuilder};
let key = b"key";
let value = b"value";
let mut storage = MockStorage::new();
storage.set(key, value);
let app = AppBuilder::default().with_storage(storage).build(no_init);
assert_eq!(value, app.storage().get(key).unwrap().as_slice());
It is worth noting that the same initialization can be implemented directly in a callback function
passed to build
method of AppBuilder
as shown in the
following example:
use cosmwasm_std::testing::MockStorage;
use cosmwasm_std::Storage;
use cw_multi_test::AppBuilder;
let key = b"key";
let value = b"value";
let app = AppBuilder::default()
.with_storage(MockStorage::new())
.build(|router, api, storage| {
storage.set(key, value);
});
assert_eq!(value, app.storage().get(key).unwrap().as_slice());
In the examples above, to keep the simple, we accessed the storage directly and focused on the chain
initialization part involving the with_storage
method of AppBuilder
.
Please note, that inside smart contract code, the storage used by the chain should be accessed through
libraries such as Storey and StoragePlus.
You can find additional information about storage in Storage chapter.
with_wasm
(WIP)
build
Since AppBuilder
follows the principles of the builder pattern, you must
always finalize the chain-building process by calling the build
method with an
initialization callback function. If no specific chain initialization is required, you can use the
provided no_init
callback. Otherwise, you can initialize the chain using a custom
callback function. An example of how to initialize a user’s balance is shown below.
use cosmwasm_std::coin;
use cw_multi_test::AppBuilder;
let my_address = "me".into_addr();
let my_funds = vec![coin(23, "ATOM"), coin(18, "FLOCK")];
let app = AppBuilder::default().build(|router, api, storage| {
router
.bank
.init_balance(storage, &my_address, my_funds)
.unwrap();
});
assert_eq!(
"23ATOM",
app.wrap()
.query_balance(my_address, "ATOM")
.unwrap()
.to_string()
);