Common Tests

To verify that our pallet code behaves as expected, it is necessary to check a few conditions with unit tests. Intuitively, the order of the testing may resemble the structure of runtime method development.

  1. Within each runtime method, declarative checks are made prior to any state change. These checks ensure that any required conditions are met before all changes occur; need to ensure that panics panic.
  2. Next, verify that the expected storage changes occurred.
  3. Finally, check that the expected events were emitted with correct values.

Checks before Changes are Enforced (i.e. Panics Panic)

The Verify First, Write Last paradigm encourages verifying certain conditions before changing storage values. In tests, it might be desirable to verify that invalid inputs return the expected error message.

In pallets/adding-machine, the runtime method add checks for overflow

decl_module! {
    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
        fn deposit_event() = default;

        fn add(origin, val1: u32, val2: u32) -> Result {
            let _ = ensure_signed(origin)?;
            // checks for overflow
            let result = match val1.checked_add(val2) {
                Some(r) => r,
                None => return Err("Addition overflowed"),
            Self::deposit_event(Event::Added(val1, val2, result));

The test below verifies that the expected error is thrown for a specific case of overflow.

fn overflow_fails() {
	ExtBuilder::build().execute_with(|| {
			AddingMachine::add(Origin::signed(3), u32::max_value(), 1),
			"Addition overflowed"

This requires importing the assert_err macro from support. With all the previous imported objects,

mod tests {
	use support::{assert_err, impl_outer_event, impl_outer_origin, parameter_types};
	// more imports and tests

For more examples, see Substrate's own pallets -- for mock runtime scaffolding and for unit tests.

Expected Changes to Storage are Triggered


Changes to storage can be checked by direct calls to the storage values. The syntax is the same as it would be in the pallet's runtime methods.

use crate::*;

fn set_value_works() {
  ExtBuilder::build().execute_with(|| {
    assert_ok!(SingleValue::set_value(Origin::signed(1), 10));
    assert_eq!(SingleValue::stored_value(), 10);
    // Another way of accessing the storage. This pattern is needed if it is a more complexed data
    //   type, e.g. StorageMap, StorageLinkedMap
    assert_eq!(<StoredValue>::get(), 10);

For context, the tested pallets's decl_storage block looks like

decl_storage! {
  trait Store for Module<T: Trait> as SingleValue {
    StoredValue get(fn stored_value): u32;
    StoredAccount get(fn stored_account): T::AccountId;

Expected Events are Emitted

The common way of testing expected event emission behavior requires importing support's impl_outer_event! macro

use support::impl_outer_event;

The TestEvent enum imports and uses the pallet's Event enum. The new local pallet, hello_substrate, re-exports the contents of the root to give a name for the current crate to impl_outer_event!.

mod hello_substrate {
	pub use crate::Event;

impl_outer_event! {
	pub enum TestEvent for TestRuntime {

impl Trait for TestRuntime {
	type Event = TestEvent;

Testing the correct emission of events compares constructions of expected events with the entries in the System::events vector of EventRecords. In pallets/adding-machine,

fn add_emits_correct_event() {
	ExtBuilder::build().execute_with(|| {
		AddingMachine::add(Origin::signed(1), 6, 9);

				EventRecord {
					phase: Phase::ApplyExtrinsic(0),
					event: TestEvent::added(crate::Event::Added(6, 9, 15)),
					topics: vec![],

This check requires importing from system

use system::{EventRecord, Phase};

A more ergonomic way of testing whether a specific event was emitted might use the System::events().iter(). This pattern doesn't require the previous imports, but it does require importing RawEvent (or Event) from the pallet and ensure_signed from system to convert signed extrinsics to the underlying AccountId,

mod tests {
	// other imports
	use system::ensure_signed;
	use super::RawEvent; // if no RawEvent, then `use super::Event;`
	// tests

In pallets/hello-substrate,

fn last_value_updates() {
	ExtBuilder::build().execute_with(|| {
		HelloSubstrate::set_value(Origin::signed(1), 10u64);
		// some assert checks

		let id_1 = ensure_signed(Origin::signed(1)).unwrap();
		let expected_event1 = TestEvent::hello_substrate(
			RawEvent::ValueSet(id_1, 10),
		assert!(System::events().iter().any(|a| a.event == expected_event1));

This test constructs an expected_event1 based on the event that the developer expects will be emitted upon the successful execution of logic in HelloSubstrate::set_value. The assert!() statement checks if the expected_event1 matches the .event field for any EventRecord in the System::events() vector.