Mock Runtime¶
Introduction¶
Testing is essential in Polkadot SDK development to ensure your blockchain operates as intended and effectively handles various potential scenarios. This guide walks you through setting up an environment to test pallets within the runtime, allowing you to evaluate how different pallets, their configurations, and system components interact to ensure reliable blockchain functionality.
Configuring a Mock Runtime¶
Testing Module¶
The mock runtime includes all the necessary pallets and configurations needed for testing. To ensure proper testing, you must create a module that integrates all components, enabling assessment of interactions between pallets and system elements.
Here's a simple example of how to create a testing module that simulates these interactions:
Note
The crate::*;
snippet imports all the components from your crate (including runtime configurations, pallet modules, and utility functions) into the tests
module. This allows you to write tests without manually importing each piece, making the code more concise and readable.
Alternatively, you can create a separate mock.rs
file to define the configuration for your mock runtime and a companion tests.rs
file to house the specific logic for each test.
Once the testing module is configured, you can craft your mock runtime using the frame_support::runtime
macro. This macro allows you to define a runtime environment that will be created for testing purposes:
pub mod tests {
use crate::*;
#[frame_support::runtime]
mod runtime {
#[runtime::runtime]
#[runtime::derive(
RuntimeCall,
RuntimeEvent,
RuntimeError,
RuntimeOrigin,
RuntimeFreezeReason,
RuntimeHoldReason,
RuntimeSlashReason,
RuntimeLockId,
RuntimeTask
)]
pub struct Test;
#[runtime::pallet_index(0)]
pub type System = frame_system::Pallet<Test>;
// Other pallets...
}
}
Genesis Storage¶
The next step is configuring the genesis storage—the initial state of your runtime. Genesis storage sets the starting conditions for the runtime, defining how pallets are configured before any blocks are produced. You can only customize the initial state only of those items that implement the [pallet::genesis_config]
and [pallet::genesis_build]
macros within their respective pallets.
In Polkadot SDK, you can create this storage using the BuildStorage
trait from the sp_runtime
crate. This trait is essential for building the configuration that initializes the blockchain's state.
The function new_test_ext()
demonstrates setting up this environment. It uses frame_system::GenesisConfig::<Test>::default()
to generate a default genesis configuration for the runtime, followed by .build_storage()
to create the initial storage state. This storage is then converted into a format usable by the testing framework, sp_io::TestExternalities
, allowing tests to be executed in a simulated blockchain environment.
Here's the code that sets the genesis storage configuration:
pub mod tests {
use crate::*;
use sp_runtime::BuildStorage;
#[frame_support::runtime]
mod runtime {
#[runtime::runtime]
#[runtime::derive(
RuntimeCall,
RuntimeEvent,
RuntimeError,
RuntimeOrigin,
RuntimeFreezeReason,
RuntimeHoldReason,
RuntimeSlashReason,
RuntimeLockId,
RuntimeTask
)]
pub struct Test;
#[runtime::pallet_index(0)]
pub type System = frame_system::Pallet<Test>;
// Other pallets...
}
pub fn new_test_ext() -> sp_io::TestExternalities {
frame_system::GenesisConfig::<Test>::default()
.build_storage()
.unwrap()
.into()
}
}
You can also customize the genesis storage to set initial values for your runtime pallets. For example, you can set the initial balance for accounts like this:
// Build genesis storage according to the runtime's configuration
pub fn new_test_ext() -> sp_io::TestExternalities {
// Define the initial balances for accounts
let initial_balances: Vec<(AccountId32, u128)> = vec![
(AccountId32::from([0u8; 32]), 1_000_000_000_000),
(AccountId32::from([1u8; 32]), 2_000_000_000_000),
];
let mut t = frame_system::GenesisConfig::<Test>::default()
.build_storage()
.unwrap();
// Adding balances configuration to the genesis config
pallet_balances::GenesisConfig::<Test> {
balances: initial_balances,
}
.assimilate_storage(&mut t)
.unwrap();
t.into()
}
Note
For a more idiomatic approach, consult the Your first pallet
guide from the Polkadot SDK rust documentation.
Pallet Configuration¶
Each pallet in the mocked runtime requires an associated configuration, specifying the types and values it depends on to function. These configurations often use basic or primitive types (e.g., u32, bool) instead of more complex types like structs or traits, ensuring the setup remains straightforward and manageable.
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
...
type Index = u64;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
...
}
impl pallet_template::Config for Test {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
...
}
The configuration should be set for each pallet existing in the mocked runtime.
Note
The simplification of types is for simplifying the testing process. For example, AccountId
is u64
, meaning a valid account address can be an unsigned integer:
Where to Go Next¶
With the mock environment in place, developers can now test and explore how pallets interact and ensure they work seamlessly together. For further details about mocking runtimes, see the following Polkadot SDK docs guide.
-
Guide Pallet Testing
Learn how to efficiently test pallets in the Polkadot SDK, ensuring your pallet operations are reliable and secure.
| Created: December 13, 2024