centrifuge / centrifuge-chain

Centrifuge Chain: the layer-1 blockchain for real-world assets, built on Substrate.
https://centrifuge.io
GNU Lesser General Public License v3.0
182 stars 81 forks source link

Mock-builder: proposal tracking #1315

Open lemunozm opened 1 year ago

lemunozm commented 1 year ago

Description

Utility for building mocks from traits that works fine with Substrate pallets.

Motivation

Loose coupling is great for avoiding the knowledge load of how other pallets work when you develop your own pallet. Nevertheless, this benefit and all the effort put into making this abstraction is lost once you need to choose an implementation for tests/benchmarks. Testing and benchmarking force you to have the dependencies and trait implementations you wanted to avoid by the usage of traits, getting the annoying extra load of knowledge again.

To fix this problem, we can need an automatic way of creating generic mocks that allows you to get rid of using real implementations in tests/benchmarks, and force you to improve the quality of your tests.

Note: by "generic mocks" I mean a mock that can be configured independently for each test case, by using closures.

Proposal

In your trait mocks folder

A place where you create all trait mocks you need.

// Build the pallet boilerplate
#[mock_builder::pallet] 
mod pallet_mock_permission {
    //What you wrote:
    #[mock::trait]
    trait Permissions<Account> {
        #[mock::call(mock_has)]
        fn has(a: Self::Scope, b: AccountId, c: Self::Role) -> bool;

        #[mock::call(mock_add)]
        fn add(a: Self::Scope, b: AccountId, c: Self::Role) -> DispatchResult;

        #[mock::call(mock_remove)]
        fn remove(a: Self::Scope, b: AccountId, c: Self::Role) -> DispatchResult;
    }

    // Another trait for the same pallet here
    #[mock::trait]
    trait OtherTrait {
        //...
    }
}

// Other pallet mocks

In your pallet mock.rs file

frame_support::construct_runtime!(
    pub enum Runtime where {
        System: frame_system,
        //...
        MockPermissions: pallet_mock_permissions,
    }
);

//...

impl pallet_mock_permissions::Config for Runtime {
    type Scope = PermissionScope<PoolId, CurrencyId>;
    type AccountId = AccountId;
    type Role = Role;
}

In your tests

#[test]
fn check_borrower() {
    let borrower = 123;

    new_test_ext().execute_with(|| {
        // For me, as a tester, I do not care how Permission should be initialized/configured,
        // I only want to check that my code behaves correctly with the values returned by it.
        MockPermission::mock_has(move |_, who, _| {
            // You can place any assert here or return any value you want.
            who == borrower
        });
        assert_ok!(Loans::borrow(borrower, ...));
    });
}

Roadmap

lemunozm commented 1 year ago

I would not formally create a proposal itself without first solving Milestone 1