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
[x] Milestone 1: Fully usable from test. Initial mock-builder implementation for using it in tests for any trait. Doesn't matter at this stage the creation ergonomics.
[x] Basic trait support: #1220
[x] Generic method support. Traits methods with bounds are not supported. Each call to a method with bounds generates under the hood a different method signature with each defined type. How do we know which type we should use when recovering the stored function from the function storage? #1316
[x] Any lifetime support. Method bounds require to be 'static when they do not really need this. This is quite annoying because we can not implement traits without that 'static. #1322
[ ] Milestone 2: Easy to generate this with procedural macros. Block by Milestone 1.
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
folderA place where you create all trait mocks you need.
In your pallet
mock.rs
fileIn your tests
Roadmap
mock-builder
implementation for using it in tests for any trait. Doesn't matter at this stage the creation ergonomics.'static
when they do not really need this. This is quite annoying because we can not implement traits without that'static
. #1322