This question is more about best practices, and if there is a better method for providing mock data for off-chain ink! tests.
The goal is to use mock data for off-chain unit testing for ink! contracts. Cross-contract calls do not work in an off-chain environment. In this example, the contract performs a cross-contract call to an ERC-20 contract to get a caller's balance. My solution was to implement conditional compiling to "re-route" a helper function.
//only compiles when *not* running tests
#[cfg(not(test))]
fn get_token_balance(&self, caller: &AccountId) -> Balance {
//calls the external ERC-20 contract
self.token.balance_of(*caller)
}
//only compiles when running tests
#[cfg(test)]
fn get_token_balance(&self, _: &AccountId) -> Balance {
//arbitrary value
1
}
When compiling for tests, the function that contains the cross-contract call will be excluded. Instead, a different version of the function will be compiled returning some arbitrary value, enabling the unit tests to run.
The general pattern would be:
- Extrapolate cross-contract calls into a helper function
- Add a configuration to the function that performs the cross-contract call to not compile when building tests
- Add another function that does compile when building in tests. This function returns any mock data that would be returned from the external contract.
One limitation of this method is that it will only work for cross-contract calls that do not perform writes but only reads. One solution to this is adding an extra field to the contract that only compiles with tests. This field will work as a fake-storage.
#[ink(storage)]
pub struct MyContract {
#[cfg(test)]
mock_field: SomeStruct, // will serve as a fake storage
}
...
//on-chain, performs cross-contract call
#[cfg(not(test))]
fn do_some_write(&mut self) {
self.external_contract.write_to_field(0xDEADBEEF);
}
//testing environment only
#[cfg(test)]
fn do_some_write(&mut self) {
self.mock_field.my_fake_storage_item = 0xDEADBEEF;
}
Is there a more streamlined approach to providing these mock tests? Or this a viable solution that future ink! developers could use to make their unit tests pass?