Repository: CosmWasm/cw-multi-test
Pull request #76 (2023-10-02 11:54:39)

Extract `AppBuilder` to separate module
To reduce the `App.rs` file size, extracted the `AppBuilder` to separate module. It simplifies analysing both of them.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index ec283b1..16bc38d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -248,6 +248,7 @@ dependencies = [
  "derivative",
  "ecdsa",
  "itertools",
+ "once_cell",
  "prost",
  "schemars",
  "serde",
diff --git a/Cargo.toml b/Cargo.toml
index 48d93e4..ee834c8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -33,6 +33,7 @@ derivative = "2.2.0"
 sha2 = "0.10.7"
 
 [dev-dependencies]
+once_cell = "1.18.0"
 # We don't use these dependencies directly,
 # we tighten versions that builds with `-Zminimal-versions` work.
 ecdsa = "0.16.8"
diff --git a/src/app.rs b/src/app.rs
index 26b4804..a82bd63 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -7,10 +7,11 @@ use crate::module::{FailingModule, Module};
 use crate::staking::{Distribution, DistributionKeeper, StakeKeeper, Staking, StakingSudo};
 use crate::transactions::transactional;
 use crate::wasm::{ContractData, Wasm, WasmKeeper, WasmSudo};
+use crate::AppBuilder;
 use anyhow::{bail, Result as AnyResult};
 use cosmwasm_std::{
     from_slice,
-    testing::{mock_env, MockApi, MockStorage},
+    testing::{MockApi, MockStorage},
     to_binary, Addr, Api, Binary, BlockInfo, ContractResult, CosmosMsg, CustomQuery, Empty, GovMsg,
     IbcMsg, IbcQuery, Querier, QuerierResult, QuerierWrapper, QueryRequest, Record, Storage,
     SystemError, SystemResult,
@@ -52,10 +53,10 @@ pub struct App<
     Ibc = FailingModule<IbcMsg, IbcQuery, Empty>,
     Gov = FailingModule<GovMsg, Empty, Empty>,
 > {
-    router: Router<Bank, Custom, Wasm, Staking, Distr, Ibc, Gov>,
-    api: Api,
-    storage: Storage,
-    block: BlockInfo,
+    pub(crate) router: Router<Bank, Custom, Wasm, Staking, Distr, Ibc, Gov>,
+    pub(crate) api: Api,
+    pub(crate) storage: Storage,
+    pub(crate) block: BlockInfo,
 }
 
 pub fn no_init<BankT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT>(
@@ -160,476 +161,6 @@ where
     }
 }
 
-/// This is essential to create a custom app with custom handler.
-///   let mut app = BasicAppBuilder::<E, Q>::new_custom().with_custom(handler).build();
-pub type BasicAppBuilder<ExecC, QueryC> = AppBuilder<
-    BankKeeper,
-    MockApi,
-    MockStorage,
-    FailingModule<ExecC, QueryC, Empty>,
-    WasmKeeper<ExecC, QueryC>,
-    StakeKeeper,
-    DistributionKeeper,
-    FailingModule<IbcMsg, IbcQuery, Empty>,
-    FailingModule<GovMsg, Empty, Empty>,
->;
-
-/// Utility to build [App] in stages. If particular items/properties are not explicitly set,
-/// then default values are used.
-pub struct AppBuilder<Bank, Api, Storage, Custom, Wasm, Staking, Distr, Ibc, Gov> {
-    api: Api,
-    block: BlockInfo,
-    storage: Storage,
-    bank: Bank,
-    wasm: Wasm,
-    custom: Custom,
-    staking: Staking,
-    distribution: Distr,
-    ibc: Ibc,
-    gov: Gov,
-}
-
-impl Default
-    for AppBuilder<
-        BankKeeper,
-        MockApi,
-        MockStorage,
-        FailingModule<Empty, Empty, Empty>,
-        WasmKeeper<Empty, Empty>,
-        StakeKeeper,
-        DistributionKeeper,
-        FailingModule<IbcMsg, IbcQuery, Empty>,
-        FailingModule<GovMsg, Empty, Empty>,
-    >
-{
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-impl
-    AppBuilder<
-        BankKeeper,
-        MockApi,
-        MockStorage,
-        FailingModule<Empty, Empty, Empty>,
-        WasmKeeper<Empty, Empty>,
-        StakeKeeper,
-        DistributionKeeper,
-        FailingModule<IbcMsg, IbcQuery, Empty>,
-        FailingModule<GovMsg, Empty, Empty>,
-    >
-{
-    /// Creates builder with default components working with empty exec and query messages.
-    pub fn new() -> Self {
-        AppBuilder {
-            api: MockApi::default(),
-            block: mock_env().block,
-            storage: MockStorage::new(),
-            bank: BankKeeper::new(),
-            wasm: WasmKeeper::new(),
-            custom: FailingModule::new(),
-            staking: StakeKeeper::new(),
-            distribution: DistributionKeeper::new(),
-            ibc: FailingModule::new(),
-            gov: FailingModule::new(),
-        }
-    }
-}
-
-impl<ExecC, QueryC>
-    AppBuilder<
-        BankKeeper,
-        MockApi,
-        MockStorage,
-        FailingModule<ExecC, QueryC, Empty>,
-        WasmKeeper<ExecC, QueryC>,
-        StakeKeeper,
-        DistributionKeeper,
-        FailingModule<IbcMsg, IbcQuery, Empty>,
-        FailingModule<GovMsg, Empty, Empty>,
-    >
-where
-    ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static,
-    QueryC: Debug + CustomQuery + DeserializeOwned + 'static,
-{
-    /// Creates builder with default components designed to work with custom exec and query
-    /// messages.
-    pub fn new_custom() -> Self {
-        AppBuilder {
-            api: MockApi::default(),
-            block: mock_env().block,
-            storage: MockStorage::new(),
-            bank: BankKeeper::new(),
-            wasm: WasmKeeper::new(),
-            custom: FailingModule::new(),
-            staking: StakeKeeper::new(),
-            distribution: DistributionKeeper::new(),
-            ibc: FailingModule::new(),
-            gov: FailingModule::new(),
-        }
-    }
-}
-
-impl<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT>
-    AppBuilder<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT>
-where
-    CustomT: Module,
-    WasmT: Wasm<CustomT::ExecT, CustomT::QueryT>,
-{
-    /// Overwrites the default wasm executor.
-    ///
-    /// At this point it is needed that new wasm implements some `Wasm` trait, but it doesn't need
-    /// to be bound to Bank or Custom yet - as those may change. The cross-components validation is
-    /// done on final building.
-    pub fn with_wasm<NewWasm: Wasm<CustomT::ExecT, CustomT::QueryT>>(
-        self,
-        wasm: NewWasm,
-    ) -> AppBuilder<BankT, ApiT, StorageT, CustomT, NewWasm, StakingT, DistrT, IbcT, GovT> {
-        let AppBuilder {
-            bank,
-            api,
-            storage,
-            custom,
-            block,
-            staking,
-            distribution,
-            ibc,
-            gov,
-            ..
-        } = self;
-
-        AppBuilder {
-            api,
-            block,
-            storage,
-            bank,
-            wasm,
-            custom,
-            staking,
-            distribution,
-            ibc,
-            gov,
-        }
-    }
-
-    /// Overwrites the default bank interface.
-    pub fn with_bank<NewBank: Bank>(
-        self,
-        bank: NewBank,
-    ) -> AppBuilder<NewBank, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT> {
-        let AppBuilder {
-            wasm,
-            api,
-            storage,
-            custom,
-            block,
-            staking,
-            distribution,
-            ibc,
-            gov,
-            ..
-        } = self;
-
-        AppBuilder {
-            api,
-            block,
-            storage,
-            bank,
-            wasm,
-            custom,
-            staking,
-            distribution,
-            ibc,
-            gov,
-        }
-    }
-
-    /// Overwrites the default api interface.
-    pub fn with_api<NewApi: Api>(
-        self,
-        api: NewApi,
-    ) -> AppBuilder<BankT, NewApi, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT> {
-        let AppBuilder {
-            wasm,
-            bank,
-            storage,
-            custom,
-            block,
-            staking,
-            distribution,
-            ibc,
-            gov,
-            ..
-        } = self;
-
-        AppBuilder {
-            api,
-            block,
-            storage,
-            bank,
-            wasm,
-            custom,
-            staking,
-            distribution,
-            ibc,
-            gov,
-        }
-    }
-
-    /// Overwrites the default storage interface.
-    pub fn with_storage<NewStorage: Storage>(
-        self,
-        storage: NewStorage,
-    ) -> AppBuilder<BankT, ApiT, NewStorage, CustomT, WasmT, StakingT, DistrT, IbcT, GovT> {
-        let AppBuilder {
-            wasm,
-            api,
-            bank,
-            custom,
-            block,
-            staking,
-            distribution,
-            ibc,
-            gov,
-            ..
-        } = self;
-
-        AppBuilder {
-            api,
-            block,
-            storage,
-            bank,
-            wasm,
-            custom,
-            staking,
-            distribution,
-            ibc,
-            gov,
-        }
-    }
-
-    /// Overwrites the default custom messages handler.
-    ///
-    /// At this point it is needed that new custom implements some `Module` trait, but it doesn't need
-    /// to be bound to ExecC or QueryC yet - as those may change. The cross-components validation is
-    /// done on final building.
-    pub fn with_custom<NewCustom: Module>(
-        self,
-        custom: NewCustom,
-    ) -> AppBuilder<BankT, ApiT, StorageT, NewCustom, WasmT, StakingT, DistrT, IbcT, GovT> {
-        let AppBuilder {
-            wasm,
-            bank,
-            api,
-            storage,
-            block,
-            staking,
-            distribution,
-            ibc,
-            gov,
-            ..
-        } = self;
-
-        AppBuilder {
-            api,
-            block,
-            storage,
-            bank,
-            wasm,
-            custom,
-            staking,
-            distribution,
-            ibc,
-            gov,
-        }
-    }
-
-    /// Overwrites the default staking interface.
-    pub fn with_staking<NewStaking: Staking>(
-        self,
-        staking: NewStaking,
-    ) -> AppBuilder<BankT, ApiT, StorageT, CustomT, WasmT, NewStaking, DistrT, IbcT, GovT> {
-        let AppBuilder {
-            wasm,
-            api,
-            storage,
-            custom,
-            block,
-            bank,
-            distribution,
-            ibc,
-            gov,
-            ..
-        } = self;
-
-        AppBuilder {
-            api,
-            block,
-            storage,
-            bank,
-            wasm,
-            custom,
-            staking,
-            distribution,
-            ibc,
-            gov,
-        }
-    }
-
-    /// Overwrites the default distribution interface.
-    pub fn with_distribution<NewDistribution: Distribution>(
-        self,
-        distribution: NewDistribution,
-    ) -> AppBuilder<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, NewDistribution, IbcT, GovT>
-    {
-        let AppBuilder {
-            wasm,
-            api,
-            storage,
-            custom,
-            block,
-            staking,
-            bank,
-            ibc,
-            gov,
-            ..
-        } = self;
-
-        AppBuilder {
-            api,
-            block,
-            storage,
-            bank,
-            wasm,
-            custom,
-            staking,
-            distribution,
-            ibc,
-            gov,
-        }
-    }
-
-    /// Overwrites the default ibc interface.
-    ///
-    /// If you wish to simply ignore/drop all returned IBC Messages,
-    /// you can use the `IbcAcceptingModule` type:
-    /// ```text
-    /// builder.with_ibc(IbcAcceptingModule::new())
-    /// ```
-    pub fn with_ibc<NewIbc: Ibc>(
-        self,
-        ibc: NewIbc,
-    ) -> AppBuilder<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, NewIbc, GovT> {
-        let AppBuilder {
-            wasm,
-            api,
-            storage,
-            custom,
-            block,
-            staking,
-            bank,
-            distribution,
-            gov,
-            ..
-        } = self;
-
-        AppBuilder {
-            api,
-            block,
-            storage,
-            bank,
-            wasm,
-            custom,
-            staking,
-            distribution,
-            ibc,
-            gov,
-        }
-    }
-
-    /// Overwrites the default gov interface.
-    pub fn with_gov<NewGov: Gov>(
-        self,
-        gov: NewGov,
-    ) -> AppBuilder<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, NewGov> {
-        let AppBuilder {
-            wasm,
-            api,
-            storage,
-            custom,
-            block,
-            staking,
-            bank,
-            distribution,
-            ibc,
-            ..
-        } = self;
-
-        AppBuilder {
-            api,
-            block,
-            storage,
-            bank,
-            wasm,
-            custom,
-            staking,
-            distribution,
-            ibc,
-            gov,
-        }
-    }
-
-    /// Overwrites the initial block.
-    pub fn with_block(mut self, block: BlockInfo) -> Self {
-        self.block = block;
-        self
-    }
-
-    /// Builds final `App`. At this point all components type have to be properly related to each
-    /// other. If there are some generics related compilation errors, make sure that all components
-    /// are properly relating to each other.
-    pub fn build<F>(
-        self,
-        init_fn: F,
-    ) -> App<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT>
-    where
-        BankT: Bank,
-        ApiT: Api,
-        StorageT: Storage,
-        CustomT: Module,
-        WasmT: Wasm<CustomT::ExecT, CustomT::QueryT>,
-        StakingT: Staking,
-        DistrT: Distribution,
-        IbcT: Ibc,
-        GovT: Gov,
-        F: FnOnce(
-            &mut Router<BankT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT>,
-            &dyn Api,
-            &mut dyn Storage,
-        ),
-    {
-        let router = Router {
-            wasm: self.wasm,
-            bank: self.bank,
-            custom: self.custom,
-            staking: self.staking,
-            distribution: self.distribution,
-            ibc: self.ibc,
-            gov: self.gov,
-        };
-
-        let mut app = App {
-            router,
-            api: self.api,
-            block: self.block,
-            storage: self.storage,
-        };
-        app.init_modules(init_fn);
-        app
-    }
-}
-
 impl<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT>
     App<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT>
 where
@@ -663,11 +194,6 @@ where
         &mut self.storage
     }
 
-    /// Returns a shared reference to application's block info.
-    pub fn block(&self) -> &BlockInfo {
-        &self.block
-    }
-
     pub fn init_modules<F, T>(&mut self, init_fn: F) -> T
     where
         F: FnOnce(
diff --git a/src/app_builder.rs b/src/app_builder.rs
new file mode 100644
index 0000000..e59579b
--- /dev/null
+++ b/src/app_builder.rs
@@ -0,0 +1,481 @@
+//! Implementation of the builder for [App].
+
+use crate::{
+    App, Bank, BankKeeper, Distribution, DistributionKeeper, FailingModule, Gov, Ibc, Module,
+    Router, StakeKeeper, Staking, Wasm, WasmKeeper,
+};
+use cosmwasm_std::testing::{mock_env, MockApi, MockStorage};
+use cosmwasm_std::{Api, BlockInfo, CustomQuery, Empty, GovMsg, IbcMsg, IbcQuery, Storage};
+use schemars::JsonSchema;
+use serde::de::DeserializeOwned;
+use std::fmt::Debug;
+
+/// This is essential to create a custom app with custom handler.
+///   let mut app = BasicAppBuilder::<E, Q>::new_custom().with_custom(handler).build();
+pub type BasicAppBuilder<ExecC, QueryC> = AppBuilder<
+    BankKeeper,
+    MockApi,
+    MockStorage,
+    FailingModule<ExecC, QueryC, Empty>,
+    WasmKeeper<ExecC, QueryC>,
+    StakeKeeper,
+    DistributionKeeper,
+    FailingModule<IbcMsg, IbcQuery, Empty>,
+    FailingModule<GovMsg, Empty, Empty>,
+>;
+
+/// Utility to build [App] in stages. If particular items/properties are not explicitly set,
+/// then default values are used.
+pub struct AppBuilder<Bank, Api, Storage, Custom, Wasm, Staking, Distr, Ibc, Gov> {
+    api: Api,
+    block: BlockInfo,
+    storage: Storage,
+    bank: Bank,
+    wasm: Wasm,
+    custom: Custom,
+    staking: Staking,
+    distribution: Distr,
+    ibc: Ibc,
+    gov: Gov,
+}
+
+impl Default
+    for AppBuilder<
+        BankKeeper,
+        MockApi,
+        MockStorage,
+        FailingModule<Empty, Empty, Empty>,
+        WasmKeeper<Empty, Empty>,
+        StakeKeeper,
+        DistributionKeeper,
+        FailingModule<IbcMsg, IbcQuery, Empty>,
+        FailingModule<GovMsg, Empty, Empty>,
+    >
+{
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl
+    AppBuilder<
+        BankKeeper,
+        MockApi,
+        MockStorage,
+        FailingModule<Empty, Empty, Empty>,
+        WasmKeeper<Empty, Empty>,
+        StakeKeeper,
+        DistributionKeeper,
+        FailingModule<IbcMsg, IbcQuery, Empty>,
+        FailingModule<GovMsg, Empty, Empty>,
+    >
+{
+    /// Creates builder with default components working with empty exec and query messages.
+    pub fn new() -> Self {
+        AppBuilder {
+            api: MockApi::default(),
+            block: mock_env().block,
+            storage: MockStorage::new(),
+            bank: BankKeeper::new(),
+            wasm: WasmKeeper::new(),
+            custom: FailingModule::new(),
+            staking: StakeKeeper::new(),
+            distribution: DistributionKeeper::new(),
+            ibc: FailingModule::new(),
+            gov: FailingModule::new(),
+        }
+    }
+}
+
+impl<ExecC, QueryC>
+    AppBuilder<
+        BankKeeper,
+        MockApi,
+        MockStorage,
+        FailingModule<ExecC, QueryC, Empty>,
+        WasmKeeper<ExecC, QueryC>,
+        StakeKeeper,
+        DistributionKeeper,
+        FailingModule<IbcMsg, IbcQuery, Empty>,
+        FailingModule<GovMsg, Empty, Empty>,
+    >
+where
+    ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static,
+    QueryC: Debug + CustomQuery + DeserializeOwned + 'static,
+{
+    /// Creates builder with default components designed to work with custom exec and query
+    /// messages.
+    pub fn new_custom() -> Self {
+        AppBuilder {
+            api: MockApi::default(),
+            block: mock_env().block,
+            storage: MockStorage::new(),
+            bank: BankKeeper::new(),
+            wasm: WasmKeeper::new(),
+            custom: FailingModule::new(),
+            staking: StakeKeeper::new(),
+            distribution: DistributionKeeper::new(),
+            ibc: FailingModule::new(),
+            gov: FailingModule::new(),
+        }
+    }
+}
+
+impl<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT>
+    AppBuilder<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT>
+where
+    CustomT: Module,
+    WasmT: Wasm<CustomT::ExecT, CustomT::QueryT>,
+{
+    /// Overwrites the default wasm executor.
+    ///
+    /// At this point it is needed that new wasm implements some `Wasm` trait, but it doesn't need
+    /// to be bound to Bank or Custom yet - as those may change. The cross-components validation is
+    /// done on final building.
+    pub fn with_wasm<NewWasm: Wasm<CustomT::ExecT, CustomT::QueryT>>(
+        self,
+        wasm: NewWasm,
+    ) -> AppBuilder<BankT, ApiT, StorageT, CustomT, NewWasm, StakingT, DistrT, IbcT, GovT> {
+        let AppBuilder {
+            bank,
+            api,
+            storage,
+            custom,
+            block,
+            staking,
+            distribution,
+            ibc,
+            gov,
+            ..
+        } = self;
+
+        AppBuilder {
+            api,
+            block,
+            storage,
+            bank,
+            wasm,
+            custom,
+            staking,
+            distribution,
+            ibc,
+            gov,
+        }
+    }
+
+    /// Overwrites the default bank interface.
+    pub fn with_bank<NewBank: Bank>(
+        self,
+        bank: NewBank,
+    ) -> AppBuilder<NewBank, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT> {
+        let AppBuilder {
+            wasm,
+            api,
+            storage,
+            custom,
+            block,
+            staking,
+            distribution,
+            ibc,
+            gov,
+            ..
+        } = self;
+
+        AppBuilder {
+            api,
+            block,
+            storage,
+            bank,
+            wasm,
+            custom,
+            staking,
+            distribution,
+            ibc,
+            gov,
+        }
+    }
+
+    /// Overwrites the default api interface.
+    pub fn with_api<NewApi: Api>(
+        self,
+        api: NewApi,
+    ) -> AppBuilder<BankT, NewApi, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT> {
+        let AppBuilder {
+            wasm,
+            bank,
+            storage,
+            custom,
+            block,
+            staking,
+            distribution,
+            ibc,
+            gov,
+            ..
+        } = self;
+
+        AppBuilder {
+            api,
+            block,
+            storage,
+            bank,
+            wasm,
+            custom,
+            staking,
+            distribution,
+            ibc,
+            gov,
+        }
+    }
+
+    /// Overwrites the default storage interface.
+    pub fn with_storage<NewStorage: Storage>(
+        self,
+        storage: NewStorage,
+    ) -> AppBuilder<BankT, ApiT, NewStorage, CustomT, WasmT, StakingT, DistrT, IbcT, GovT> {
+        let AppBuilder {
+            wasm,
+            api,
+            bank,
+            custom,
+            block,
+            staking,
+            distribution,
+            ibc,
+            gov,
+            ..
+        } = self;
+
+        AppBuilder {
+            api,
+            block,
+            storage,
+            bank,
+            wasm,
+            custom,
+            staking,
+            distribution,
+            ibc,
+            gov,
+        }
+    }
+
+    /// Overwrites the default custom messages handler.
+    ///
+    /// At this point it is needed that new custom implements some `Module` trait, but it doesn't need
+    /// to be bound to ExecC or QueryC yet - as those may change. The cross-components validation is
+    /// done on final building.
+    pub fn with_custom<NewCustom: Module>(
+        self,
+        custom: NewCustom,
+    ) -> AppBuilder<BankT, ApiT, StorageT, NewCustom, WasmT, StakingT, DistrT, IbcT, GovT> {
+        let AppBuilder {
+            wasm,
+            bank,
+            api,
+            storage,
+            block,
+            staking,
+            distribution,
+            ibc,
+            gov,
+            ..
+        } = self;
+
+        AppBuilder {
+            api,
+            block,
+            storage,
+            bank,
+            wasm,
+            custom,
+            staking,
+            distribution,
+            ibc,
+            gov,
+        }
+    }
+
+    /// Overwrites the default staking interface.
+    pub fn with_staking<NewStaking: Staking>(
+        self,
+        staking: NewStaking,
+    ) -> AppBuilder<BankT, ApiT, StorageT, CustomT, WasmT, NewStaking, DistrT, IbcT, GovT> {
+        let AppBuilder {
+            wasm,
+            api,
+            storage,
+            custom,
+            block,
+            bank,
+            distribution,
+            ibc,
+            gov,
+            ..
+        } = self;
+
+        AppBuilder {
+            api,
+            block,
+            storage,
+            bank,
+            wasm,
+            custom,
+            staking,
+            distribution,
+            ibc,
+            gov,
+        }
+    }
+
+    /// Overwrites the default distribution interface.
+    pub fn with_distribution<NewDistribution: Distribution>(
+        self,
+        distribution: NewDistribution,
+    ) -> AppBuilder<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, NewDistribution, IbcT, GovT>
+    {
+        let AppBuilder {
+            wasm,
+            api,
+            storage,
+            custom,
+            block,
+            staking,
+            bank,
+            ibc,
+            gov,
+            ..
+        } = self;
+
+        AppBuilder {
+            api,
+            block,
+            storage,
+            bank,
+            wasm,
+            custom,
+            staking,
+            distribution,
+            ibc,
+            gov,
+        }
+    }
+
+    /// Overwrites the default ibc interface.
+    ///
+    /// If you wish to simply ignore/drop all returned IBC Messages,
+    /// you can use the `IbcAcceptingModule` type:
+    /// ```text
+    /// builder.with_ibc(IbcAcceptingModule::new())
+    /// ```
+    pub fn with_ibc<NewIbc: Ibc>(
+        self,
+        ibc: NewIbc,
+    ) -> AppBuilder<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, NewIbc, GovT> {
+        let AppBuilder {
+            wasm,
+            api,
+            storage,
+            custom,
+            block,
+            staking,
+            bank,
+            distribution,
+            gov,
+            ..
+        } = self;
+
+        AppBuilder {
+            api,
+            block,
+            storage,
+            bank,
+            wasm,
+            custom,
+            staking,
+            distribution,
+            ibc,
+            gov,
+        }
+    }
+
+    /// Overwrites the default gov interface.
+    pub fn with_gov<NewGov: Gov>(
+        self,
+        gov: NewGov,
+    ) -> AppBuilder<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, NewGov> {
+        let AppBuilder {
+            wasm,
+            api,
+            storage,
+            custom,
+            block,
+            staking,
+            bank,
+            distribution,
+            ibc,
+            ..
+        } = self;
+
+        AppBuilder {
+            api,
+            block,
+            storage,
+            bank,
+            wasm,
+            custom,
+            staking,
+            distribution,
+            ibc,
+            gov,
+        }
+    }
+
+    /// Overwrites the initial block.
+    pub fn with_block(mut self, block: BlockInfo) -> Self {
+        self.block = block;
+        self
+    }
+
+    /// Builds final `App`. At this point all components type have to be properly related to each
+    /// other. If there are some generics related compilation errors, make sure that all components
+    /// are properly relating to each other.
+    pub fn build<F>(
+        self,
+        init_fn: F,
+    ) -> App<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT>
+    where
+        BankT: Bank,
+        ApiT: Api,
+        StorageT: Storage,
+        CustomT: Module,
+        WasmT: Wasm<CustomT::ExecT, CustomT::QueryT>,
+        StakingT: Staking,
+        DistrT: Distribution,
+        IbcT: Ibc,
+        GovT: Gov,
+        F: FnOnce(
+            &mut Router<BankT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT>,
+            &dyn Api,
+            &mut dyn Storage,
+        ),
+    {
+        let router = Router {
+            wasm: self.wasm,
+            bank: self.bank,
+            custom: self.custom,
+            staking: self.staking,
+            distribution: self.distribution,
+            ibc: self.ibc,
+            gov: self.gov,
+        };
+
+        let mut app = App {
+            router,
+            api: self.api,
+            block: self.block,
+            storage: self.storage,
+        };
+        app.init_modules(init_fn);
+        app
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index ab166c3..1d90986 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -7,6 +7,7 @@
 //! To understand the design of this module, please refer to `../DESIGN.md`
 
 mod app;
+mod app_builder;
 mod bank;
 #[allow(clippy::type_complexity)]
 mod contracts;
@@ -23,10 +24,8 @@ mod tests;
 mod transactions;
 mod wasm;
 
-pub use crate::app::{
-    custom_app, next_block, App, AppBuilder, BasicApp, BasicAppBuilder, CosmosRouter, Router,
-    SudoMsg,
-};
+pub use crate::app::{custom_app, next_block, App, BasicApp, CosmosRouter, Router, SudoMsg};
+pub use crate::app_builder::{AppBuilder, BasicAppBuilder};
 pub use crate::bank::{Bank, BankKeeper, BankSudo};
 pub use crate::contracts::{Contract, ContractWrapper};
 pub use crate::executor::{AppResponse, Executor};


Test patch:
diff --git a/src/tests/app.rs b/src/tests/app.rs
index 1fcedf8..c22d005 100644
--- a/src/tests/app.rs
+++ b/src/tests/app.rs
@@ -95,11 +95,11 @@ where
 fn update_block() {
     let mut app = App::default();
 
-    let BlockInfo { time, height, .. } = app.block().clone();
+    let BlockInfo { time, height, .. } = app.block_info();
     app.update_block(next_block);
 
-    assert_eq!(time.plus_seconds(5), app.block().time);
-    assert_eq!(height + 1, app.block().height);
+    assert_eq!(time.plus_seconds(5), app.block_info().time);
+    assert_eq!(height + 1, app.block_info().height);
 }
 
 #[test]
@@ -126,7 +126,7 @@ fn multi_level_bank_cache() {
         .execute(
             app.api(),
             &mut cache,
-            app.block(),
+            &app.block_info(),
             owner.clone(),
             msg.into(),
         )
@@ -145,7 +145,7 @@ fn multi_level_bank_cache() {
             amount: coins(12, "eth"),
         };
         app.router()
-            .execute(app.api(), cache2, app.block(), owner, msg.into())
+            .execute(app.api(), cache2, &app.block_info(), owner, msg.into())
             .unwrap();
 
         // shows up in 2nd cache
diff --git a/tests/app_builder/mod.rs b/tests/app_builder/mod.rs
index 42eecf3..3d0a4da 100644
--- a/tests/app_builder/mod.rs
+++ b/tests/app_builder/mod.rs
@@ -1,1 +1,142 @@
+use anyhow::{bail, Result as AnyResult};
+use cosmwasm_std::{Addr, Api, Binary, BlockInfo, CustomQuery, Querier, Storage};
+use cw_multi_test::{AppResponse, CosmosRouter, Module};
+use cw_storage_plus::Item;
+use schemars::JsonSchema;
+use serde::de::DeserializeOwned;
+use serde::{Deserialize, Serialize};
+use std::fmt::Debug;
+use std::marker::PhantomData;
+
+mod with_bank;
+mod with_block;
+mod with_distribution;
+mod with_staking;
+mod with_storage;
 mod with_wasm;
+
+const COUNTER: Item<u64> = Item::new("count");
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case")]
+enum CounterQueryMsg {
+    Counter {},
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct CounterResponseMsg {
+    value: u64,
+}
+
+mod contracts {
+    use super::*;
+
+    pub mod counter {
+        use super::*;
+        use cosmwasm_std::{
+            to_binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdError, WasmMsg,
+        };
+        use cw_multi_test::{Contract, ContractWrapper};
+
+        fn instantiate(
+            deps: DepsMut,
+            _env: Env,
+            _info: MessageInfo,
+            _msg: Empty,
+        ) -> Result<Response, StdError> {
+            COUNTER.save(deps.storage, &1).unwrap();
+            Ok(Response::default())
+        }
+
+        fn execute(
+            deps: DepsMut,
+            _env: Env,
+            _info: MessageInfo,
+            _msg: WasmMsg,
+        ) -> Result<Response, StdError> {
+            if let Some(mut counter) = COUNTER.may_load(deps.storage).unwrap() {
+                counter += 1;
+                COUNTER.save(deps.storage, &counter).unwrap();
+            }
+            Ok(Response::default())
+        }
+
+        fn query(deps: Deps, _env: Env, msg: CounterQueryMsg) -> Result<Binary, StdError> {
+            match msg {
+                CounterQueryMsg::Counter { .. } => Ok(to_binary(&CounterResponseMsg {
+                    value: COUNTER.may_load(deps.storage).unwrap().unwrap(),
+                })?),
+            }
+        }
+
+        pub fn contract() -> Box<dyn Contract<Empty>> {
+            Box::new(ContractWrapper::new_with_empty(execute, instantiate, query))
+        }
+    }
+}
+
+struct MyKeeper<ExecT, QueryT, SudoT>(
+    PhantomData<(ExecT, QueryT, SudoT)>,
+    &'static str,
+    &'static str,
+    &'static str,
+);
+
+impl<ExecT, QueryT, SudoT> MyKeeper<ExecT, QueryT, SudoT> {
+    fn new(execute_msg: &'static str, query_msg: &'static str, sudo_msg: &'static str) -> Self {
+        Self(Default::default(), execute_msg, query_msg, sudo_msg)
+    }
+}
+
+impl<ExecT, QueryT, SudoT> Module for MyKeeper<ExecT, QueryT, SudoT>
+where
+    ExecT: Debug,
+    QueryT: Debug,
+    SudoT: Debug,
+{
+    type ExecT = ExecT;
+    type QueryT = QueryT;
+    type SudoT = SudoT;
+
+    fn execute<ExecC, QueryC>(
+        &self,
+        _api: &dyn Api,
+        _storage: &mut dyn Storage,
+        _router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
+        _block: &BlockInfo,
+        _sender: Addr,
+        _msg: Self::ExecT,
+    ) -> AnyResult<AppResponse>
+    where
+        ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static,
+        QueryC: CustomQuery + DeserializeOwned + 'static,
+    {
+        bail!(self.1);
+    }
+
+    fn sudo<ExecC, QueryC>(
+        &self,
+        _api: &dyn Api,
+        _storage: &mut dyn Storage,
+        _router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
+        _block: &BlockInfo,
+        _msg: Self::SudoT,
+    ) -> AnyResult<AppResponse>
+    where
+        ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static,
+        QueryC: CustomQuery + DeserializeOwned + 'static,
+    {
+        bail!(self.3);
+    }
+
+    fn query(
+        &self,
+        _api: &dyn Api,
+        _storage: &dyn Storage,
+        _querier: &dyn Querier,
+        _block: &BlockInfo,
+        _request: Self::QueryT,
+    ) -> AnyResult<Binary> {
+        bail!(self.2);
+    }
+}
diff --git a/tests/app_builder/with_bank.rs b/tests/app_builder/with_bank.rs
new file mode 100644
index 0000000..5f6ebf5
--- /dev/null
+++ b/tests/app_builder/with_bank.rs
@@ -0,0 +1,63 @@
+use crate::app_builder::MyKeeper;
+use cosmwasm_std::{coins, Addr, BankMsg, BankQuery};
+use cw_multi_test::{AppBuilder, Bank, BankSudo, Executor};
+
+type MyBankKeeper = MyKeeper<BankMsg, BankQuery, BankSudo>;
+
+impl Bank for MyBankKeeper {}
+
+const EXECUTE_MSG: &str = "bank execute called";
+const QUERY_MSG: &str = "bank query called";
+const SUDO_MSG: &str = "bank sudo called";
+
+#[test]
+fn building_app_with_custom_bank_should_work() {
+    // build custom bank keeper
+    let bank_keeper = MyBankKeeper::new(EXECUTE_MSG, QUERY_MSG, SUDO_MSG);
+
+    // build the application with custom bank keeper
+    let app_builder = AppBuilder::default();
+    let mut app = app_builder.with_bank(bank_keeper).build(|_, _, _| {});
+
+    // prepare additional input data
+    let recipient = Addr::unchecked("recipient");
+    let denom = "eth";
+
+    // executing bank message should return an error defined in custom keeper
+    assert_eq!(
+        EXECUTE_MSG,
+        app.execute(
+            Addr::unchecked("sender"),
+            BankMsg::Send {
+                to_address: recipient.clone().into(),
+                amount: coins(1, denom),
+            }
+            .into(),
+        )
+        .unwrap_err()
+        .to_string()
+    );
+
+    // executing bank sudo should return an error defined in custom keeper
+    assert_eq!(
+        SUDO_MSG,
+        app.sudo(
+            BankSudo::Mint {
+                to_address: recipient.clone().into(),
+                amount: vec![],
+            }
+            .into()
+        )
+        .unwrap_err()
+        .to_string()
+    );
+
+    // executing bank query should return an error defined in custom keeper
+    assert_eq!(
+        format!("Generic error: Querier contract error: {}", QUERY_MSG),
+        app.wrap()
+            .query_balance(recipient, denom)
+            .unwrap_err()
+            .to_string()
+    );
+}
diff --git a/tests/app_builder/with_block.rs b/tests/app_builder/with_block.rs
new file mode 100644
index 0000000..18178ba
--- /dev/null
+++ b/tests/app_builder/with_block.rs
@@ -0,0 +1,21 @@
+use cosmwasm_std::{BlockInfo, Timestamp};
+use cw_multi_test::AppBuilder;
+
+#[test]
+fn building_app_with_custom_block_should_work() {
+    // prepare additional test data
+    let block_info = BlockInfo {
+        height: 20,
+        time: Timestamp::from_nanos(1_571_797_419_879_305_544),
+        chain_id: "my-testnet".to_string(),
+    };
+
+    // build the application with custom block
+    let app_builder = AppBuilder::default();
+    let app = app_builder
+        .with_block(block_info.clone())
+        .build(|_, _, _| {});
+
+    // calling block_info should return the same block used during initialization
+    assert_eq!(block_info, app.block_info());
+}
diff --git a/tests/app_builder/with_distribution.rs b/tests/app_builder/with_distribution.rs
new file mode 100644
index 0000000..4067400
--- /dev/null
+++ b/tests/app_builder/with_distribution.rs
@@ -0,0 +1,39 @@
+use crate::app_builder::MyKeeper;
+use cosmwasm_std::{Addr, DistributionMsg, Empty};
+use cw_multi_test::{AppBuilder, Distribution, Executor};
+
+type MyDistributionKeeper = MyKeeper<DistributionMsg, Empty, Empty>;
+
+impl Distribution for MyDistributionKeeper {}
+
+const EXECUTE_MSG: &str = "distribution execute called";
+
+#[test]
+fn building_app_with_custom_distribution_should_work() {
+    // build custom distribution keeper
+    // distribution keeper has no query or sudo messages
+    let distribution_keeper = MyDistributionKeeper::new(EXECUTE_MSG, "", "");
+
+    // build the application with custom distribution keeper
+    let app_builder = AppBuilder::default();
+    let mut app = app_builder
+        .with_distribution(distribution_keeper)
+        .build(|_, _, _| {});
+
+    // prepare additional input data
+    let recipient = Addr::unchecked("recipient");
+
+    // executing distribution message should return an error defined in custom keeper
+    assert_eq!(
+        EXECUTE_MSG,
+        app.execute(
+            Addr::unchecked("sender"),
+            DistributionMsg::SetWithdrawAddress {
+                address: recipient.into(),
+            }
+            .into(),
+        )
+        .unwrap_err()
+        .to_string()
+    );
+}
diff --git a/tests/app_builder/with_staking.rs b/tests/app_builder/with_staking.rs
new file mode 100644
index 0000000..89b3b56
--- /dev/null
+++ b/tests/app_builder/with_staking.rs
@@ -0,0 +1,60 @@
+use crate::app_builder::MyKeeper;
+use cosmwasm_std::{Addr, Coin, StakingMsg, StakingQuery};
+use cw_multi_test::{AppBuilder, Executor, Staking, StakingSudo};
+
+type MyStakeKeeper = MyKeeper<StakingMsg, StakingQuery, StakingSudo>;
+
+impl Staking for MyStakeKeeper {}
+
+const EXECUTE_MSG: &str = "staking execute called";
+const QUERY_MSG: &str = "staking query called";
+const SUDO_MSG: &str = "staking sudo called";
+
+#[test]
+#[cfg(feature = "staking")]
+fn building_app_with_custom_staking_should_work() {
+    // build custom stake keeper
+    let stake_keeper = MyStakeKeeper::new(EXECUTE_MSG, QUERY_MSG, SUDO_MSG);
+
+    // build the application with custom stake keeper
+    let app_builder = AppBuilder::default();
+    let mut app = app_builder.with_staking(stake_keeper).build(|_, _, _| {});
+
+    // prepare additional input data
+    let validator = Addr::unchecked("recipient");
+
+    // executing staking message should return an error defined in custom keeper
+    assert_eq!(
+        EXECUTE_MSG,
+        app.execute(
+            Addr::unchecked("sender"),
+            StakingMsg::Delegate {
+                validator: validator.clone().into(),
+                amount: Coin::new(1, "eth"),
+            }
+            .into(),
+        )
+        .unwrap_err()
+        .to_string()
+    );
+
+    // executing staking sudo should return an error defined in custom keeper
+    assert_eq!(
+        SUDO_MSG,
+        app.sudo(
+            StakingSudo::Slash {
+                validator: validator.into(),
+                percentage: Default::default(),
+            }
+            .into()
+        )
+        .unwrap_err()
+        .to_string()
+    );
+
+    // executing staking query should return an error defined in custom keeper
+    assert_eq!(
+        format!("Generic error: Querier contract error: {}", QUERY_MSG),
+        app.wrap().query_all_validators().unwrap_err().to_string()
+    );
+}
diff --git a/tests/app_builder/with_storage.rs b/tests/app_builder/with_storage.rs
new file mode 100644
index 0000000..08c01cd
--- /dev/null
+++ b/tests/app_builder/with_storage.rs
@@ -0,0 +1,91 @@
+use crate::app_builder::{contracts, CounterQueryMsg, CounterResponseMsg};
+use cosmwasm_std::{to_binary, Addr, Empty, Order, Record, Storage, WasmMsg};
+use cw_multi_test::{AppBuilder, Executor};
+use std::collections::BTreeMap;
+use std::iter;
+
+#[derive(Default)]
+struct MyStorage(BTreeMap<Vec<u8>, Vec<u8>>);
+
+// Minimal implementation of custom storage.
+impl Storage for MyStorage {
+    fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
+        self.0.get::<Vec<u8>>(&key.into()).cloned()
+    }
+
+    fn range<'a>(
+        &'a self,
+        _start: Option<&[u8]>,
+        _end: Option<&[u8]>,
+        _order: Order,
+    ) -> Box<dyn Iterator<Item = Record> + 'a> {
+        Box::new(iter::empty())
+    }
+
+    fn set(&mut self, key: &[u8], value: &[u8]) {
+        self.0.insert(key.into(), value.into());
+    }
+
+    fn remove(&mut self, key: &[u8]) {
+        self.0.remove(key);
+    }
+}
+
+#[test]
+fn building_app_with_custom_storage_should_work() {
+    // prepare additional test input data
+    let owner = Addr::unchecked("owner");
+    let msg = to_binary(&Empty {}).unwrap();
+    let admin = None;
+    let funds = vec![];
+    let label = "my-counter";
+
+    // build the application with custom storage
+    let app_builder = AppBuilder::default();
+    let mut app = app_builder
+        .with_storage(MyStorage::default())
+        .build(|_, _, _| {});
+
+    // store a contract code
+    let code_id = app.store_code(contracts::counter::contract());
+
+    // instantiate contract, this initializes a counter with value 1
+    let contract_addr = app
+        .instantiate_contract(
+            code_id,
+            owner.clone(),
+            &WasmMsg::Instantiate {
+                admin: admin.clone(),
+                code_id,
+                msg: msg.clone(),
+                funds: funds.clone(),
+                label: label.into(),
+            },
+            &funds,
+            label,
+            admin,
+        )
+        .unwrap();
+
+    // execute contract, this increments a counter
+    app.execute_contract(
+        owner,
+        contract_addr.clone(),
+        &WasmMsg::Execute {
+            contract_addr: contract_addr.clone().into(),
+            msg,
+            funds,
+        },
+        &[],
+    )
+    .unwrap();
+
+    // query contract for current counter value
+    let response: CounterResponseMsg = app
+        .wrap()
+        .query_wasm_smart(&contract_addr, &CounterQueryMsg::Counter {})
+        .unwrap();
+
+    // counter should be 2
+    assert_eq!(2, response.value);
+}
diff --git a/tests/app_builder/with_wasm.rs b/tests/app_builder/with_wasm.rs
index 2361e0a..640bc7d 100644
--- a/tests/app_builder/with_wasm.rs
+++ b/tests/app_builder/with_wasm.rs
@@ -1,126 +1,160 @@
-use anyhow::Result as AnyResult;
+use crate::app_builder::{contracts, MyKeeper};
+use anyhow::{bail, Result as AnyResult};
 use cosmwasm_std::{
-    Addr, Api, Binary, BlockInfo, Deps, DepsMut, Empty, Env, MessageInfo, Querier, Record,
-    Response, StdError, Storage, SubMsg, WasmMsg, WasmQuery,
+    Addr, Api, Binary, BlockInfo, Empty, Querier, Record, Storage, WasmMsg, WasmQuery,
 };
 use cw_multi_test::{
-    AppBuilder, AppResponse, Contract, ContractData, ContractWrapper, CosmosRouter, Wasm,
+    AppBuilder, AppResponse, Contract, ContractData, CosmosRouter, Executor, Wasm, WasmKeeper,
+    WasmSudo,
 };
-use schemars::JsonSchema;
-use std::fmt::Debug;
-use std::marker::PhantomData;
-
-mod contracts {
-    use super::*;
-
-    pub mod caller {
-        use super::*;
-
-        fn instantiate(
-            _deps: DepsMut,
-            _env: Env,
-            _info: MessageInfo,
-            _msg: Empty,
-        ) -> Result<Response, StdError> {
-            Ok(Response::default())
-        }
-
-        fn execute(
-            _deps: DepsMut,
-            _env: Env,
-            _info: MessageInfo,
-            msg: WasmMsg,
-        ) -> Result<Response, StdError> {
-            let message = SubMsg::new(msg);
-
-            Ok(Response::new().add_submessage(message))
-        }
-
-        fn query(_deps: Deps, _env: Env, _msg: Empty) -> Result<Binary, StdError> {
-            Err(StdError::generic_err(
-                "query not implemented for the `caller` contract",
-            ))
-        }
-
-        pub fn contract<C>() -> Box<dyn Contract<C>>
-        where
-            C: Clone + Debug + PartialEq + JsonSchema + 'static,
-        {
-            let contract = ContractWrapper::new_with_empty(execute, instantiate, query);
-            Box::new(contract)
-        }
+use once_cell::sync::Lazy;
+
+const EXECUTE_MSG: &str = "wasm execute called";
+const QUERY_MSG: &str = "wasm query called";
+const SUDO_MSG: &str = "wasm sudo called";
+const DUPLICATE_CODE_MSG: &str = "wasm duplicate code called";
+const CONTRACT_DATA_MSG: &str = "wasm contract data called";
+
+const CODE_ID: u64 = 154;
+
+static WASM_RAW: Lazy<Vec<Record>> = Lazy::new(|| vec![(vec![154u8], vec![155u8])]);
+
+// This is on purpose derived from module, to check if there are no compilation errors
+// when custom wasm keeper implements also Module trait (although it is not needed).
+type MyWasmKeeper = MyKeeper<Empty, Empty, Empty>;
+
+impl<ExecT, QueryT> Wasm<ExecT, QueryT> for MyWasmKeeper {
+    fn query(
+        &self,
+        _api: &dyn Api,
+        _storage: &dyn Storage,
+        _querier: &dyn Querier,
+        _block: &BlockInfo,
+        _request: WasmQuery,
+    ) -> AnyResult<Binary> {
+        bail!(self.2);
     }
-}
 
-#[test]
-fn building_app_with_custom_wasm_should_work() {
-    struct MyWasm<ExecT, QueryT>(PhantomData<(ExecT, QueryT)>);
+    fn execute(
+        &self,
+        _api: &dyn Api,
+        _storage: &mut dyn Storage,
+        _router: &dyn CosmosRouter<ExecC = ExecT, QueryC = QueryT>,
+        _block: &BlockInfo,
+        _sender: Addr,
+        _msg: WasmMsg,
+    ) -> AnyResult<AppResponse> {
+        bail!(self.1);
+    }
+
+    fn sudo(
+        &self,
+        _api: &dyn Api,
+        _contract_addr: Addr,
+        _storage: &mut dyn Storage,
+        _router: &dyn CosmosRouter<ExecC = ExecT, QueryC = QueryT>,
+        _block: &BlockInfo,
+        _msg: Binary,
+    ) -> AnyResult<AppResponse> {
+        bail!(self.3);
+    }
+
+    fn store_code(&mut self, _creator: Addr, _code: Box<dyn Contract<ExecT, QueryT>>) -> u64 {
+        CODE_ID
+    }
+
+    fn duplicate_code(&mut self, _code_id: u64) -> AnyResult<u64> {
+        bail!(DUPLICATE_CODE_MSG);
+    }
 
-    impl<ExecT, QueryT> Default for MyWasm<ExecT, QueryT> {
-        fn default() -> Self {
-            Self(PhantomData)
-        }
+    fn contract_data(&self, _storage: &dyn Storage, _address: &Addr) -> AnyResult<ContractData> {
+        bail!(CONTRACT_DATA_MSG);
     }
 
-    impl<ExecT, QueryT> Wasm<ExecT, QueryT> for MyWasm<ExecT, QueryT> {
-        fn query(
-            &self,
-            _api: &dyn Api,
-            _storage: &dyn Storage,
-            _querier: &dyn Querier,
-            _block: &BlockInfo,
-            _request: WasmQuery,
-        ) -> AnyResult<Binary> {
-            todo!()
-        }
-
-        fn execute(
-            &self,
-            _api: &dyn Api,
-            _storage: &mut dyn Storage,
-            _router: &dyn CosmosRouter<ExecC = ExecT, QueryC = QueryT>,
-            _block: &BlockInfo,
-            _sender: Addr,
-            _msg: WasmMsg,
-        ) -> AnyResult<AppResponse> {
-            todo!()
-        }
-
-        fn sudo(
-            &self,
-            _api: &dyn Api,
-            _contract_addr: Addr,
-            _storage: &mut dyn Storage,
-            _router: &dyn CosmosRouter<ExecC = ExecT, QueryC = QueryT>,
-            _block: &BlockInfo,
-            _msg: Binary,
-        ) -> AnyResult<AppResponse> {
-            todo!()
-        }
-
-        fn store_code(&mut self, _creator: Addr, _code: Box<dyn Contract<ExecT, QueryT>>) -> u64 {
-            154
-        }
-
-        fn duplicate_code(&mut self, _code_id: u64) -> AnyResult<u64> {
-            todo!()
-        }
-
-        fn contract_data(
-            &self,
-            _storage: &dyn Storage,
-            _address: &Addr,
-        ) -> AnyResult<ContractData> {
-            todo!()
-        }
-
-        fn dump_wasm_raw(&self, _storage: &dyn Storage, _address: &Addr) -> Vec<Record> {
-            todo!()
-        }
+    fn dump_wasm_raw(&self, _storage: &dyn Storage, _address: &Addr) -> Vec<Record> {
+        WASM_RAW.clone()
     }
+}
+
+#[test]
+fn building_app_with_custom_wasm_should_work() {
+    // build custom wasm keeper
+    let wasm_keeper = MyWasmKeeper::new(EXECUTE_MSG, QUERY_MSG, SUDO_MSG);
 
+    // build the application with custom wasm keeper
+    let app_builder = AppBuilder::default();
+    let mut app = app_builder.with_wasm(wasm_keeper).build(|_, _, _| {});
+
+    // prepare additional input data
+    let contract_addr = Addr::unchecked("contract");
+
+    // calling store_code should return value defined in custom keeper
+    assert_eq!(CODE_ID, app.store_code(contracts::counter::contract()));
+
+    // calling duplicate_code should return error defined in custom keeper
+    assert_eq!(
+        DUPLICATE_CODE_MSG,
+        app.duplicate_code(CODE_ID).unwrap_err().to_string()
+    );
+
+    // calling contract_data should return error defined in custom keeper
+    assert_eq!(
+        CONTRACT_DATA_MSG,
+        app.contract_data(&contract_addr).unwrap_err().to_string()
+    );
+
+    // calling dump_wasm_raw should return value defined in custom keeper
+    assert_eq!(*WASM_RAW, app.dump_wasm_raw(&contract_addr));
+
+    // executing wasm execute should return an error defined in custom keeper
+    assert_eq!(
+        EXECUTE_MSG,
+        app.execute(
+            Addr::unchecked("sender"),
+            WasmMsg::Instantiate {
+                admin: None,
+                code_id: 0,
+                msg: Default::default(),
+                funds: vec![],
+                label: "".to_string(),
+            }
+            .into()
+        )
+        .unwrap_err()
+        .to_string()
+    );
+
+    // executing wasm sudo should return an error defined in custom keeper
+    assert_eq!(
+        SUDO_MSG,
+        app.sudo(
+            WasmSudo {
+                contract_addr,
+                msg: Default::default()
+            }
+            .into()
+        )
+        .unwrap_err()
+        .to_string()
+    );
+
+    // executing wasm query should return an error defined in custom keeper
+    #[cfg(feature = "cosmwasm_1_2")]
+    assert_eq!(
+        format!("Generic error: Querier contract error: {}", QUERY_MSG),
+        app.wrap()
+            .query_wasm_code_info(CODE_ID)
+            .unwrap_err()
+            .to_string()
+    );
+}
+
+#[test]
+fn compiling_with_wasm_keeper_should_work() {
+    // this verifies only compilation errors
+    // while our WasmKeeper does not implement Module
     let app_builder = AppBuilder::default();
-    let mut app = app_builder.with_wasm(MyWasm::default()).build(|_, _, _| {});
-    let code_id = app.store_code(contracts::caller::contract());
-    assert_eq!(154, code_id);
+    let _ = app_builder
+        .with_wasm(WasmKeeper::default())
+        .build(|_, _, _| {});
 }