0

I'm referring to this doc: https://docs.substrate.io/how-to-guides/v3/testing/basics/

Is it just me or are the steps incomplete? For instance, it expects an add_value extrinsic to be implemented somewhere. Also, do we need to create a Cargo.toml for this, or else how would "cargo test" be able to locate it?

2 Answers 2

3

Below are some steps you can take to get the tests working similar to the tutorial referenced.

  1. Clone down the substrate-node-template:
git clone https://github.com/substrate-developer-hub/substrate-node-template
  1. Create a MaxValue constant in your pallet's runtime:

pallets/template/src/lib.rs

#[pallet::config]
pub trait Config: frame_system::Config {
    /// Because this pallet emits events, it depends on the runtime's definition of an event.
    type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
    #[pallet::constant]
    type MaxValue: Get<u32>;
}
  1. Create a Total storage item:

pallets/template/src/lib.rs

#[pallet::storage]
#[pallet::getter(fn total)]
pub type Total<T> = StorageValue<_, u32>;
  1. Create a TotalUpdated event:

pallets/template/src/lib.rs

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
    /// Event documentation should end with an array that provides descriptive names for event
    /// parameters. [something, who]
    TotalUpdated(u32, T::AccountId),
}
  1. Create a ValueBiggerThanMax error:

pallets/template/src/lib.rs

#[pallet::error]
pub enum Error<T> {
    /// Errors should have helpful documentation associated with them.
    StorageOverflow,
    /// Value must be <= MaxValue constant
    ValueBiggerThanMax
}
  1. Create the add_value function:

pallets/template/src/lib.rs

#[pallet::weight(10_000 + T::DbWeight::get().writes(1))]
pub fn add_value(origin: OriginFor<T>, value: u32) -> DispatchResult {
    let who = ensure_signed(origin)?;

    ensure!(value <= T::MaxValue::get(), Error::<T>::ValueBiggerThanMax);

    // Read the total from storage.
    match <Total<T>>::get() {
        // Return an error if there is no total.
        None => {
            // If None, set storage to value
            <Total<T>>::put(value);
            // Emit an event.
            Self::deposit_event(Event::TotalUpdated(value, who));
            // Return a successful DispatchResultWithPostInfo
            Ok(())
        },
        // If total already exists, then get sum of value and total
        Some(curr_total) => {
            // Add the value to total
            let new_total = curr_total.checked_add(value).ok_or(Error::<T>::StorageOverflow)?;
            // Update the total in storage
            <Total<T>>::put(new_total);
            // Emit an event.
            Self::deposit_event(Event::TotalUpdated(new_total, who));
            // Return a successful DispatchResultWithPostInfo
            Ok(())
        },
    }
}
  1. Okay now let's setup the mock for our tests. Let's give our pallet a name so we can reference it in our mock file. Add this to line 1 of our mock file:

pallets/template/src/mock.rs

use crate as pallet_template;
  1. Next let's configure our pallet to work with our mock:

pallets/template/src/mock.rs

frame_support::parameter_types! {
    pub const MaxValue: u32 = 50;
}

impl pallet_template::Config for Test {
    type Event = Event;
    type MaxValue = MaxValue;
}
  1. And configure the mock runtime to work with our pallet:

pallets/template/src/mock.rs

frame_support::construct_runtime!(
    pub enum Test where
        Block = Block,
        NodeBlock = Block,
        UncheckedExtrinsic = UncheckedExtrinsic,
    {
        System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
        TemplateModule: pallet_template::{Pallet, Call, Storage, Event<T>},
    }
);
  1. Create the tests!

pallets/template/src/tests.rs

use crate::{mock::*, Error};
use frame_support::{assert_ok, assert_err};

#[test]
fn test_add_value() {
    new_test_ext().execute_with(|| {
        assert_ok!(TestingPallet::add_value(Origin::signed(1), 10));
        assert_eq!(TestingPallet::total(), Some(10));
    })
}

#[test]
fn test_multiple_add_value() {
    new_test_ext().execute_with(|| {
        assert_ok!(TestingPallet::add_value(Origin::signed(1), 10));
        assert_ok!(TestingPallet::add_value(Origin::signed(1), 20));
        assert_eq!(TestingPallet::total(), Some(30));
    })
}

#[test]
fn test_max_value() {
    new_test_ext().execute_with(|| {
        assert_err!(
            TestingPallet::add_value(Origin::signed(1), 51), 
            Error::<Test>::ValueBiggerThanMax
        );
    })
}
  1. Finally, in our runtime let's make sure we support the MaxValue that we created in our config earlier.

runtime/src/lib.rs

parameter_types! {
    pub const MaxValue: u32 = 50;
}

/// Configure the pallet-template in pallets/template.
impl pallet_template::Config for Runtime {
    type Event = Event;
    type MaxValue = MaxValue;
}

We can now test by running the following command:

cargo test

You should get some green like this:

test tests::test_max_value ... ok
test tests::test_add_value ... ok
test tests::test_multiple_add_value ... ok

Full working code here.

1
  • 2
    Awesome, thank you for the very detailed response
    – Antonio
    May 22, 2022 at 12:04
1

The guide you're referencing assumes you have some pallet with that dispatchable to test. Bruno's response gives all the context you need but this may help others too.

As the guide specifies, it assumes that:

there's some arbitrary pallet — called pallet-testing — that takes an origin and a u32 and returns Ok(()) if the value is less than or equal to a constant called MaxValue [... and contains] a single function called add_value.

The point of this guide is to show the basic pattern of setting up a test for any dispatchable call. So in order to mirror the guide, this "arbitrary" function add_function would look like:

#[pallet::call]
impl<T: Config> Pallet<T> {
    #[pallet::weight(0)]

    pub fn add_value(origin: OriginFor<T>, val_to_add: u32) -> DispatchResultWithPostInfo {
        let _ = ensure_signed(origin)?;

        ensure!(
            val_to_add <= T::MaxAddend::get(),
            "value must be <= maximum add amount constant"
        );

        // previous value got
        let c_val = SingleValue::<T>::get();

        // checks for overflow when new value added
        let result = c_val.checked_add(val_to_add).ok_or(Error::<T>::Overflow)?;

        // update storage
        <SingleValue<T>>::put(result);

        // deposit event
        Self::deposit_event(Event::Added(c_val, val_to_add, result));
        Ok(().into())
    }
}

Further, for it to work exactly as the guide shows, you'd also need to:

  • Update your pallet's configuration trait to include type MaxAddend: Get<u32>;
  • Rename the pallet to pallet_testing
  • Update your runtime configuration
1
  • Thanks for the details Sacha
    – Antonio
    May 22, 2022 at 12:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.