Repository: CosmWasm/cw-multi-test
Pull request #92 (2023-10-20 11:09:21)

Refactored modules
- Added `AcceptingModule` like existing `FailingModule`.
- Refactored accepting module in Ibc - now uses common `AcceptingModule`.
- Added type aliases for commonly used Ibc and Goc failing modules.
- Added tests for building `App` with custom Ibc and Gov modules.
- Refactored tests.


Patch:
diff --git a/src/app.rs b/src/app.rs
index 7f6d180..4c3497a 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -8,12 +8,12 @@ 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 crate::{AppBuilder, GovFailingModule, IbcFailingModule};
 use cosmwasm_std::testing::{MockApi, MockStorage};
 use cosmwasm_std::{
     from_slice, to_binary, Addr, Api, Binary, BlockInfo, ContractResult, CosmosMsg, CustomQuery,
-    Empty, GovMsg, IbcMsg, IbcQuery, Querier, QuerierResult, QuerierWrapper, QueryRequest, Record,
-    Storage, SystemError, SystemResult,
+    Empty, Querier, QuerierResult, QuerierWrapper, QueryRequest, Record, Storage, SystemError,
+    SystemResult,
 };
 use schemars::JsonSchema;
 use serde::{de::DeserializeOwned, Serialize};
@@ -34,7 +34,8 @@ pub type BasicApp<ExecC = Empty, QueryC = Empty> = App<
     WasmKeeper<ExecC, QueryC>,
     StakeKeeper,
     DistributionKeeper,
-    FailingModule<IbcMsg, IbcQuery, Empty>,
+    IbcFailingModule,
+    GovFailingModule,
 >;
 
 /// Router is a persisted state. You can query this.
@@ -49,8 +50,8 @@ pub struct App<
     Wasm = WasmKeeper<Empty, Empty>,
     Staking = StakeKeeper,
     Distr = DistributionKeeper,
-    Ibc = FailingModule<IbcMsg, IbcQuery, Empty>,
-    Gov = FailingModule<GovMsg, Empty, Empty>,
+    Ibc = IbcFailingModule,
+    Gov = GovFailingModule,
 > {
     pub(crate) router: Router<Bank, Custom, Wasm, Staking, Distr, Ibc, Gov>,
     pub(crate) api: Api,
@@ -82,8 +83,8 @@ impl BasicApp {
                 WasmKeeper<Empty, Empty>,
                 StakeKeeper,
                 DistributionKeeper,
-                FailingModule<IbcMsg, IbcQuery, Empty>,
-                FailingModule<GovMsg, Empty, Empty>,
+                IbcFailingModule,
+                GovFailingModule,
             >,
             &dyn Api,
             &mut dyn Storage,
@@ -106,8 +107,8 @@ where
             WasmKeeper<ExecC, QueryC>,
             StakeKeeper,
             DistributionKeeper,
-            FailingModule<IbcMsg, IbcQuery, Empty>,
-            FailingModule<GovMsg, Empty, Empty>,
+            IbcFailingModule,
+            GovFailingModule,
         >,
         &dyn Api,
         &mut dyn Storage,
diff --git a/src/app_builder.rs b/src/app_builder.rs
index 607ab0c..884a556 100644
--- a/src/app_builder.rs
+++ b/src/app_builder.rs
@@ -1,11 +1,11 @@
 //! Implementation of the builder for [App].
 
 use crate::{
-    App, Bank, BankKeeper, Distribution, DistributionKeeper, FailingModule, Gov, Ibc, Module,
-    Router, StakeKeeper, Staking, Wasm, WasmKeeper,
+    App, Bank, BankKeeper, Distribution, DistributionKeeper, FailingModule, Gov, GovFailingModule,
+    Ibc, IbcFailingModule, 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 cosmwasm_std::{Api, BlockInfo, CustomQuery, Empty, Storage};
 use schemars::JsonSchema;
 use serde::de::DeserializeOwned;
 use std::fmt::Debug;
@@ -33,8 +33,8 @@ pub type BasicAppBuilder<ExecC, QueryC> = AppBuilder<
     WasmKeeper<ExecC, QueryC>,
     StakeKeeper,
     DistributionKeeper,
-    FailingModule<IbcMsg, IbcQuery, Empty>,
-    FailingModule<GovMsg, Empty, Empty>,
+    IbcFailingModule,
+    GovFailingModule,
 >;
 
 /// Utility to build [App] in stages.
@@ -61,8 +61,8 @@ impl Default
         WasmKeeper<Empty, Empty>,
         StakeKeeper,
         DistributionKeeper,
-        FailingModule<IbcMsg, IbcQuery, Empty>,
-        FailingModule<GovMsg, Empty, Empty>,
+        IbcFailingModule,
+        GovFailingModule,
     >
 {
     fn default() -> Self {
@@ -79,8 +79,8 @@ impl
         WasmKeeper<Empty, Empty>,
         StakeKeeper,
         DistributionKeeper,
-        FailingModule<IbcMsg, IbcQuery, Empty>,
-        FailingModule<GovMsg, Empty, Empty>,
+        IbcFailingModule,
+        GovFailingModule,
     >
 {
     /// Creates builder with default components working with empty exec and query messages.
@@ -94,8 +94,8 @@ impl
             custom: FailingModule::new(),
             staking: StakeKeeper::new(),
             distribution: DistributionKeeper::new(),
-            ibc: FailingModule::new(),
-            gov: FailingModule::new(),
+            ibc: IbcFailingModule::new(),
+            gov: GovFailingModule::new(),
         }
     }
 }
@@ -109,8 +109,8 @@ impl<ExecC, QueryC>
         WasmKeeper<ExecC, QueryC>,
         StakeKeeper,
         DistributionKeeper,
-        FailingModule<IbcMsg, IbcQuery, Empty>,
-        FailingModule<GovMsg, Empty, Empty>,
+        IbcFailingModule,
+        GovFailingModule,
     >
 where
     ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static,
@@ -128,8 +128,8 @@ where
             custom: FailingModule::new(),
             staking: StakeKeeper::new(),
             distribution: DistributionKeeper::new(),
-            ibc: FailingModule::new(),
-            gov: FailingModule::new(),
+            ibc: IbcFailingModule::new(),
+            gov: GovFailingModule::new(),
         }
     }
 }
diff --git a/src/gov.rs b/src/gov.rs
index e84d430..286333a 100644
--- a/src/gov.rs
+++ b/src/gov.rs
@@ -1,6 +1,12 @@
-use crate::{FailingModule, Module};
+use crate::{AcceptingModule, FailingModule, Module};
 use cosmwasm_std::{Empty, GovMsg};
 
 pub trait Gov: Module<ExecT = GovMsg, QueryT = Empty, SudoT = Empty> {}
 
-impl Gov for FailingModule<GovMsg, Empty, Empty> {}
+pub type GovAcceptingModule = AcceptingModule<GovMsg, Empty, Empty>;
+
+impl Gov for GovAcceptingModule {}
+
+pub type GovFailingModule = FailingModule<GovMsg, Empty, Empty>;
+
+impl Gov for GovFailingModule {}
diff --git a/src/ibc.rs b/src/ibc.rs
index 4a7da3c..97e5b02 100644
--- a/src/ibc.rs
+++ b/src/ibc.rs
@@ -1,62 +1,12 @@
-use crate::error::AnyResult;
-use crate::{AppResponse, FailingModule, Module};
-use cosmwasm_std::{Binary, Empty, IbcMsg, IbcQuery};
-use schemars::JsonSchema;
-use serde::de::DeserializeOwned;
-use std::fmt::Debug;
+use crate::{AcceptingModule, FailingModule, Module};
+use cosmwasm_std::{Empty, IbcMsg, IbcQuery};
 
 pub trait Ibc: Module<ExecT = IbcMsg, QueryT = IbcQuery, SudoT = Empty> {}
 
-impl Ibc for FailingModule<IbcMsg, IbcQuery, Empty> {}
+pub type IbcAcceptingModule = AcceptingModule<IbcMsg, IbcQuery, Empty>;
 
-pub struct IbcAcceptingModule;
-
-impl Module for IbcAcceptingModule {
-    type ExecT = IbcMsg;
-    type QueryT = IbcQuery;
-    type SudoT = Empty;
-
-    fn execute<ExecC, QueryC>(
-        &self,
-        _api: &dyn cosmwasm_std::Api,
-        _storage: &mut dyn cosmwasm_std::Storage,
-        _router: &dyn crate::CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
-        _block: &cosmwasm_std::BlockInfo,
-        _sender: cosmwasm_std::Addr,
-        _msg: Self::ExecT,
-    ) -> AnyResult<AppResponse>
-    where
-        ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static,
-        QueryC: cosmwasm_std::CustomQuery + DeserializeOwned + 'static,
-    {
-        Ok(AppResponse::default())
-    }
-
-    fn sudo<ExecC, QueryC>(
-        &self,
-        _api: &dyn cosmwasm_std::Api,
-        _storage: &mut dyn cosmwasm_std::Storage,
-        _router: &dyn crate::CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
-        _block: &cosmwasm_std::BlockInfo,
-        _msg: Self::SudoT,
-    ) -> AnyResult<AppResponse>
-    where
-        ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static,
-        QueryC: cosmwasm_std::CustomQuery + DeserializeOwned + 'static,
-    {
-        Ok(AppResponse::default())
-    }
+impl Ibc for IbcAcceptingModule {}
 
-    fn query(
-        &self,
-        _api: &dyn cosmwasm_std::Api,
-        _storage: &dyn cosmwasm_std::Storage,
-        _querier: &dyn cosmwasm_std::Querier,
-        _block: &cosmwasm_std::BlockInfo,
-        _request: Self::QueryT,
-    ) -> AnyResult<Binary> {
-        Ok(Binary::default())
-    }
-}
+pub type IbcFailingModule = FailingModule<IbcMsg, IbcQuery, Empty>;
 
-impl Ibc for IbcAcceptingModule {}
+impl Ibc for IbcFailingModule {}
diff --git a/src/lib.rs b/src/lib.rs
index 2ebde75..dfcbbdf 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -33,9 +33,9 @@ pub use crate::bank::{Bank, BankKeeper, BankSudo};
 pub use crate::checksums::ChecksumGenerator;
 pub use crate::contracts::{Contract, ContractWrapper};
 pub use crate::executor::{AppResponse, Executor};
-pub use crate::gov::Gov;
-pub use crate::ibc::{Ibc, IbcAcceptingModule};
-pub use crate::module::{FailingModule, Module};
+pub use crate::gov::{Gov, GovAcceptingModule, GovFailingModule};
+pub use crate::ibc::{Ibc, IbcAcceptingModule, IbcFailingModule};
+pub use crate::module::{AcceptingModule, FailingModule, Module};
 pub use crate::staking::{
     Distribution, DistributionKeeper, StakeKeeper, Staking, StakingInfo, StakingSudo,
 };
diff --git a/src/module.rs b/src/module.rs
index 1a8c44f..3bbe452 100644
--- a/src/module.rs
+++ b/src/module.rs
@@ -28,11 +28,22 @@ pub trait Module {
         ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static,
         QueryC: CustomQuery + DeserializeOwned + 'static;
 
+    /// Runs any [QueryT](Self::QueryT) message,
+    /// which can be called by any external actor or smart contract.
+    fn query(
+        &self,
+        api: &dyn Api,
+        storage: &dyn Storage,
+        querier: &dyn Querier,
+        block: &BlockInfo,
+        request: Self::QueryT,
+    ) -> AnyResult<Binary>;
+
     /// Runs privileged actions, like minting tokens, or governance proposals.
     /// This allows modules to have full access to these privileged actions,
     /// that cannot be triggered by smart contracts.
     ///
-    /// There is no sender, as this must be previously authorized before the call
+    /// There is no sender, as this must be previously authorized before calling.
     fn sudo<ExecC, QueryC>(
         &self,
         api: &dyn Api,
@@ -44,17 +55,6 @@ pub trait Module {
     where
         ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static,
         QueryC: CustomQuery + DeserializeOwned + 'static;
-
-    /// Runs any [QueryT](Self::QueryT) message,
-    /// which can be called by any external actor or smart contract.
-    fn query(
-        &self,
-        api: &dyn Api,
-        storage: &dyn Storage,
-        querier: &dyn Querier,
-        block: &BlockInfo,
-        request: Self::QueryT,
-    ) -> AnyResult<Binary>;
 }
 
 pub struct FailingModule<ExecT, QueryT, SudoT>(PhantomData<(ExecT, QueryT, SudoT)>);
@@ -94,6 +94,18 @@ where
         bail!("Unexpected exec msg {:?} from {:?}", msg, sender)
     }
 
+    /// Runs any [QueryT](Self::QueryT) message, always returns an error.
+    fn query(
+        &self,
+        _api: &dyn Api,
+        _storage: &dyn Storage,
+        _querier: &dyn Querier,
+        _block: &BlockInfo,
+        request: Self::QueryT,
+    ) -> AnyResult<Binary> {
+        bail!("Unexpected custom query {:?}", request)
+    }
+
     /// Runs any [SudoT](Self::SudoT) privileged action, always returns an error.
     fn sudo<ExecC, QueryC>(
         &self,
@@ -105,16 +117,66 @@ where
     ) -> AnyResult<AppResponse> {
         bail!("Unexpected sudo msg {:?}", msg)
     }
+}
 
-    /// Runs any [QueryT](Self::QueryT) message, always returns an error.
+pub struct AcceptingModule<ExecT, QueryT, SudoT>(PhantomData<(ExecT, QueryT, SudoT)>);
+
+impl<ExecT, QueryT, SudoT> AcceptingModule<ExecT, QueryT, SudoT> {
+    pub fn new() -> Self {
+        Self(PhantomData)
+    }
+}
+
+impl<ExecT, QueryT, SudoT> Default for AcceptingModule<ExecT, QueryT, SudoT> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl<ExecT, QueryT, SudoT> Module for AcceptingModule<ExecT, QueryT, SudoT>
+where
+    ExecT: Debug,
+    QueryT: Debug,
+    SudoT: Debug,
+{
+    type ExecT = ExecT;
+    type QueryT = QueryT;
+    type SudoT = SudoT;
+
+    /// Runs any [ExecT](Self::ExecT) message, always returns a default response.
+    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> {
+        Ok(AppResponse::default())
+    }
+
+    /// Runs any [QueryT](Self::QueryT) message, always returns an empty binary.
     fn query(
         &self,
         _api: &dyn Api,
         _storage: &dyn Storage,
         _querier: &dyn Querier,
         _block: &BlockInfo,
-        request: Self::QueryT,
+        _request: Self::QueryT,
     ) -> AnyResult<Binary> {
-        bail!("Unexpected custom query {:?}", request)
+        Ok(Binary::default())
+    }
+
+    /// Runs any [SudoT](Self::SudoT) privileged action, always returns a default response.
+    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> {
+        Ok(AppResponse::default())
     }
 }
diff --git a/src/staking.rs b/src/staking.rs
index 88811ce..62034c3 100644
--- a/src/staking.rs
+++ b/src/staking.rs
@@ -987,14 +987,15 @@ impl Module for DistributionKeeper {
 
 #[cfg(test)]
 mod test {
-    use crate::{app::MockRouter, BankKeeper, FailingModule, Router, WasmKeeper};
-
     use super::*;
-
+    use crate::{
+        app::MockRouter, BankKeeper, FailingModule, GovFailingModule, IbcFailingModule, Router,
+        WasmKeeper,
+    };
     use cosmwasm_std::{
         from_slice,
         testing::{mock_env, MockApi, MockStorage},
-        BalanceResponse, BankQuery, GovMsg, IbcMsg, IbcQuery,
+        BalanceResponse, BankQuery,
     };
 
     /// Type alias for default build `Router` to make its reference in typical scenario
@@ -1004,8 +1005,8 @@ mod test {
         WasmKeeper<ExecC, QueryC>,
         StakeKeeper,
         DistributionKeeper,
-        FailingModule<IbcMsg, IbcQuery, Empty>,
-        FailingModule<GovMsg, Empty, Empty>,
+        IbcFailingModule,
+        GovFailingModule,
     >;
 
     fn mock_router() -> BasicRouter {
@@ -1015,8 +1016,8 @@ mod test {
             custom: FailingModule::new(),
             staking: StakeKeeper::new(),
             distribution: DistributionKeeper::new(),
-            ibc: FailingModule::new(),
-            gov: FailingModule::new(),
+            ibc: IbcFailingModule::new(),
+            gov: GovFailingModule::new(),
         }
     }
 
diff --git a/src/wasm.rs b/src/wasm.rs
index 01c4885..00e93ea 100644
--- a/src/wasm.rs
+++ b/src/wasm.rs
@@ -1119,10 +1119,11 @@ mod test {
     use crate::staking::{DistributionKeeper, StakeKeeper};
     use crate::test_helpers::{caller, error, payout};
     use crate::transactions::StorageTransaction;
+    use crate::{GovFailingModule, IbcFailingModule};
     use cosmwasm_std::testing::{mock_env, mock_info, MockApi, MockQuerier, MockStorage};
     use cosmwasm_std::{
-        coin, from_slice, to_vec, BankMsg, CanonicalAddr, Coin, CosmosMsg, Empty, GovMsg,
-        HexBinary, IbcMsg, IbcQuery, StdError,
+        coin, from_slice, to_vec, BankMsg, CanonicalAddr, Coin, CosmosMsg, Empty, HexBinary,
+        StdError,
     };
 
     /// Type alias for default build `Router` to make its reference in typical scenario
@@ -1132,8 +1133,8 @@ mod test {
         WasmKeeper<ExecC, QueryC>,
         StakeKeeper,
         DistributionKeeper,
-        FailingModule<IbcMsg, IbcQuery, Empty>,
-        FailingModule<GovMsg, Empty, Empty>,
+        IbcFailingModule,
+        GovFailingModule,
     >;
 
     fn wasm_keeper() -> WasmKeeper<Empty, Empty> {
@@ -1147,8 +1148,8 @@ mod test {
             custom: FailingModule::new(),
             staking: StakeKeeper::new(),
             distribution: DistributionKeeper::new(),
-            ibc: FailingModule::new(),
-            gov: FailingModule::new(),
+            ibc: IbcFailingModule::new(),
+            gov: GovFailingModule::new(),
         }
     }
 


Test patch:
diff --git a/src/tests/test_ibc.rs b/src/tests/test_ibc.rs
index ff19651..2497478 100644
--- a/src/tests/test_ibc.rs
+++ b/src/tests/test_ibc.rs
@@ -24,7 +24,7 @@ fn default_ibc() {
 #[test]
 fn substituting_ibc() {
     let mut app = AppBuilder::new()
-        .with_ibc(IbcAcceptingModule)
+        .with_ibc(IbcAcceptingModule::new())
         .build(|_, _, _| ());
     let code = app.store_code(stargate::contract());
     let contract = app
diff --git a/tests/test_app_builder/mod.rs b/tests/test_app_builder/mod.rs
index a22c68c..2a2412e 100644
--- a/tests/test_app_builder/mod.rs
+++ b/tests/test_app_builder/mod.rs
@@ -10,10 +10,14 @@ mod test_with_api;
 mod test_with_bank;
 mod test_with_block;
 mod test_with_distribution;
+mod test_with_gov;
+mod test_with_ibc;
 mod test_with_staking;
 mod test_with_storage;
 mod test_with_wasm;
 
+const NO_MESSAGE: &str = "";
+
 struct MyKeeper<ExecT, QueryT, SudoT>(
     PhantomData<(ExecT, QueryT, SudoT)>,
     &'static str,
diff --git a/tests/test_app_builder/test_with_distribution.rs b/tests/test_app_builder/test_with_distribution.rs
index 7bf2b93..a3ec713 100644
--- a/tests/test_app_builder/test_with_distribution.rs
+++ b/tests/test_app_builder/test_with_distribution.rs
@@ -1,4 +1,4 @@
-use crate::test_app_builder::MyKeeper;
+use crate::test_app_builder::{MyKeeper, NO_MESSAGE};
 use cosmwasm_std::{Addr, DistributionMsg, Empty};
 use cw_multi_test::{AppBuilder, Distribution, Executor};
 
@@ -12,7 +12,7 @@ const EXECUTE_MSG: &str = "distribution execute called";
 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, "", "");
+    let distribution_keeper = MyDistributionKeeper::new(EXECUTE_MSG, NO_MESSAGE, NO_MESSAGE);
 
     // build the application with custom distribution keeper
     let app_builder = AppBuilder::default();
diff --git a/tests/test_app_builder/test_with_gov.rs b/tests/test_app_builder/test_with_gov.rs
new file mode 100644
index 0000000..92536c8
--- /dev/null
+++ b/tests/test_app_builder/test_with_gov.rs
@@ -0,0 +1,34 @@
+use crate::test_app_builder::{MyKeeper, NO_MESSAGE};
+use cosmwasm_std::{Addr, Empty, GovMsg, VoteOption};
+use cw_multi_test::{AppBuilder, Executor, Gov};
+
+type MyGovKeeper = MyKeeper<GovMsg, Empty, Empty>;
+
+impl Gov for MyGovKeeper {}
+
+const EXECUTE_MSG: &str = "gov execute called";
+
+#[test]
+fn building_app_with_custom_gov_should_work() {
+    // build custom gov keeper (no query and sudo handling for gov)
+    let gov_keeper = MyGovKeeper::new(EXECUTE_MSG, NO_MESSAGE, NO_MESSAGE);
+
+    // build the application with custom gov keeper
+    let app_builder = AppBuilder::default();
+    let mut app = app_builder.with_gov(gov_keeper).build(|_, _, _| {});
+
+    // executing gov message should return an error defined in custom keeper
+    assert_eq!(
+        EXECUTE_MSG,
+        app.execute(
+            Addr::unchecked("sender"),
+            GovMsg::Vote {
+                proposal_id: 1,
+                vote: VoteOption::Yes,
+            }
+            .into(),
+        )
+        .unwrap_err()
+        .to_string()
+    );
+}
diff --git a/tests/test_app_builder/test_with_ibc.rs b/tests/test_app_builder/test_with_ibc.rs
new file mode 100644
index 0000000..4e4e8a9
--- /dev/null
+++ b/tests/test_app_builder/test_with_ibc.rs
@@ -0,0 +1,45 @@
+use crate::test_app_builder::{MyKeeper, NO_MESSAGE};
+use cosmwasm_std::{Addr, Empty, IbcMsg, IbcQuery, QueryRequest};
+use cw_multi_test::{AppBuilder, Executor, Ibc};
+
+type MyIbcKeeper = MyKeeper<IbcMsg, IbcQuery, Empty>;
+
+impl Ibc for MyIbcKeeper {}
+
+const EXECUTE_MSG: &str = "ibc execute called";
+const QUERY_MSG: &str = "ibc query called";
+
+#[test]
+fn building_app_with_custom_ibc_should_work() {
+    // build custom ibc keeper (no sudo handling for ibc)
+    let ibc_keeper = MyIbcKeeper::new(EXECUTE_MSG, QUERY_MSG, NO_MESSAGE);
+
+    // build the application with custom ibc keeper
+    let app_builder = AppBuilder::default();
+    let mut app = app_builder.with_ibc(ibc_keeper).build(|_, _, _| {});
+
+    // executing ibc message should return an error defined in custom keeper
+    assert_eq!(
+        EXECUTE_MSG,
+        app.execute(
+            Addr::unchecked("sender"),
+            IbcMsg::CloseChannel {
+                channel_id: "my-channel".to_string()
+            }
+            .into(),
+        )
+        .unwrap_err()
+        .to_string()
+    );
+
+    // executing ibc query should return an error defined in custom keeper
+    assert_eq!(
+        format!("Generic error: Querier contract error: {}", QUERY_MSG),
+        app.wrap()
+            .query::<IbcQuery>(&QueryRequest::Ibc(IbcQuery::ListChannels {
+                port_id: Some("my-port".to_string())
+            }))
+            .unwrap_err()
+            .to_string()
+    );
+}
diff --git a/tests/test_module/mod.rs b/tests/test_module/mod.rs
index e5509da..8ccf49c 100644
--- a/tests/test_module/mod.rs
+++ b/tests/test_module/mod.rs
@@ -1,1 +1,2 @@
+mod test_accepting_module;
 mod test_failing_module;
diff --git a/tests/test_module/test_accepting_module.rs b/tests/test_module/test_accepting_module.rs
new file mode 100644
index 0000000..1952b41
--- /dev/null
+++ b/tests/test_module/test_accepting_module.rs
@@ -0,0 +1,62 @@
+use cosmwasm_std::testing::MockStorage;
+use cosmwasm_std::{Addr, Binary, Empty};
+use cw_multi_test::{AcceptingModule, App, AppResponse, Module};
+
+/// Utility function for comparing responses.
+fn eq(actual: AppResponse, expected: AppResponse) {
+    assert_eq!(actual.events, expected.events);
+    assert_eq!(actual.data, expected.data);
+}
+
+/// Utility function for asserting default outputs returned from accepting module.
+fn assert_results(accepting_module: AcceptingModule<Empty, Empty, Empty>) {
+    let app = App::default();
+    let mut storage = MockStorage::default();
+    eq(
+        AppResponse::default(),
+        accepting_module
+            .execute(
+                app.api(),
+                &mut storage,
+                app.router(),
+                &app.block_info(),
+                Addr::unchecked("sender"),
+                Empty {},
+            )
+            .unwrap(),
+    );
+    assert_eq!(
+        Binary::default(),
+        accepting_module
+            .query(
+                app.api(),
+                &storage,
+                &(*app.wrap()),
+                &app.block_info(),
+                Empty {}
+            )
+            .unwrap()
+    );
+    eq(
+        AppResponse::default(),
+        accepting_module
+            .sudo(
+                app.api(),
+                &mut storage,
+                app.router(),
+                &app.block_info(),
+                Empty {},
+            )
+            .unwrap(),
+    );
+}
+
+#[test]
+fn accepting_module_default_works() {
+    assert_results(AcceptingModule::default());
+}
+
+#[test]
+fn accepting_module_new_works() {
+    assert_results(AcceptingModule::new());
+}
diff --git a/tests/test_module/test_failing_module.rs b/tests/test_module/test_failing_module.rs
index 41ef44c..64c5a2c 100644
--- a/tests/test_module/test_failing_module.rs
+++ b/tests/test_module/test_failing_module.rs
@@ -2,9 +2,8 @@ use cosmwasm_std::testing::MockStorage;
 use cosmwasm_std::{Addr, Empty};
 use cw_multi_test::{App, FailingModule, Module};
 
-#[test]
-fn failing_module_default_works() {
-    let failing_module: FailingModule<Empty, Empty, Empty> = FailingModule::default();
+/// Utility function for asserting outputs returned from failing module.
+fn assert_results(failing_module: FailingModule<Empty, Empty, Empty>) {
     let app = App::default();
     let mut storage = MockStorage::default();
     assert_eq!(
@@ -49,49 +48,12 @@ fn failing_module_default_works() {
     );
 }
 
+#[test]
+fn failing_module_default_works() {
+    assert_results(FailingModule::default());
+}
+
 #[test]
 fn failing_module_new_works() {
-    let failing_module: FailingModule<Empty, Empty, Empty> = FailingModule::new();
-    let app = App::default();
-    let mut storage = MockStorage::default();
-    assert_eq!(
-        r#"Unexpected exec msg Empty from Addr("sender")"#,
-        failing_module
-            .execute(
-                app.api(),
-                &mut storage,
-                app.router(),
-                &app.block_info(),
-                Addr::unchecked("sender"),
-                Empty {}
-            )
-            .unwrap_err()
-            .to_string()
-    );
-    assert_eq!(
-        "Unexpected custom query Empty",
-        failing_module
-            .query(
-                app.api(),
-                &storage,
-                &(*app.wrap()),
-                &app.block_info(),
-                Empty {}
-            )
-            .unwrap_err()
-            .to_string()
-    );
-    assert_eq!(
-        "Unexpected sudo msg Empty",
-        failing_module
-            .sudo(
-                app.api(),
-                &mut storage,
-                app.router(),
-                &app.block_info(),
-                Empty {}
-            )
-            .unwrap_err()
-            .to_string()
-    );
+    assert_results(FailingModule::new());
 }