feat: integration test helpers (#15556)
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
// Integration contains the integration test setup used for SDK modules.
|
||||
package integration
|
||||
@@ -0,0 +1,150 @@
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
storetypes "cosmossdk.io/store/types"
|
||||
"github.com/cosmos/cosmos-sdk/runtime"
|
||||
"github.com/cosmos/cosmos-sdk/testutil/integration"
|
||||
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
|
||||
authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/mint"
|
||||
mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper"
|
||||
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
// Example shows how to use the integration test framework to test the integration of SDK modules.
|
||||
// Panics are used in this example, but in a real test case, you should use the testing.T object and assertions.
|
||||
func Example() {
|
||||
// in this example we are testing the integration of the following modules:
|
||||
// - mint, which directly depends on auth, bank and staking
|
||||
encodingCfg := moduletestutil.MakeTestEncodingConfig(auth.AppModuleBasic{}, mint.AppModuleBasic{})
|
||||
keys := storetypes.NewKVStoreKeys(authtypes.StoreKey, minttypes.StoreKey)
|
||||
authority := authtypes.NewModuleAddress("gov").String()
|
||||
|
||||
accountKeeper := authkeeper.NewAccountKeeper(
|
||||
encodingCfg.Codec,
|
||||
runtime.NewKVStoreService(keys[authtypes.StoreKey]),
|
||||
authtypes.ProtoBaseAccount,
|
||||
map[string][]string{minttypes.ModuleName: {authtypes.Minter}},
|
||||
"cosmos",
|
||||
authority,
|
||||
)
|
||||
|
||||
// subspace is nil because we don't test params (which is legacy anyway)
|
||||
authModule := auth.NewAppModule(encodingCfg.Codec, accountKeeper, authsims.RandomGenesisAccounts, nil)
|
||||
|
||||
// here bankkeeper and staking keeper is nil because we are not testing them
|
||||
// subspace is nil because we don't test params (which is legacy anyway)
|
||||
mintKeeper := mintkeeper.NewKeeper(encodingCfg.Codec, keys[minttypes.StoreKey], nil, accountKeeper, nil, authtypes.FeeCollectorName, authority)
|
||||
mintModule := mint.NewAppModule(encodingCfg.Codec, mintKeeper, accountKeeper, nil, nil)
|
||||
|
||||
// create the application and register all the modules from the previous step
|
||||
// replace the name and the logger by testing values in a real test case (e.g. t.Name() and log.NewTestLogger(t))
|
||||
integrationApp := integration.NewIntegrationApp("example", log.NewLogger(io.Discard), keys, authModule, mintModule)
|
||||
|
||||
// register the message and query servers
|
||||
authtypes.RegisterMsgServer(integrationApp.MsgServiceRouter(), authkeeper.NewMsgServerImpl(accountKeeper))
|
||||
minttypes.RegisterMsgServer(integrationApp.MsgServiceRouter(), mintkeeper.NewMsgServerImpl(mintKeeper))
|
||||
minttypes.RegisterQueryServer(integrationApp.QueryHelper(), mintKeeper)
|
||||
|
||||
params := minttypes.DefaultParams()
|
||||
params.BlocksPerYear = 10000
|
||||
|
||||
// now we can use the application to test a mint message
|
||||
result, err := integrationApp.RunMsg(&minttypes.MsgUpdateParams{
|
||||
Authority: authority,
|
||||
Params: params,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// in this example the result is an empty response, a nil check is enough
|
||||
// in other cases, it is recommended to check the result value.
|
||||
if result == nil {
|
||||
panic(fmt.Errorf("unexpected nil result"))
|
||||
}
|
||||
|
||||
// we now check the result
|
||||
resp := minttypes.MsgUpdateParamsResponse{}
|
||||
err = encodingCfg.Codec.Unmarshal(result.Value, &resp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// we should also check the state of the application
|
||||
got := mintKeeper.GetParams(integrationApp.SDKContext())
|
||||
if diff := cmp.Diff(got, params); diff != "" {
|
||||
panic(diff)
|
||||
}
|
||||
fmt.Println(got.BlocksPerYear)
|
||||
// Output: 10000
|
||||
}
|
||||
|
||||
// ExampleOneModule shows how to use the integration test framework to test the integration of a single module.
|
||||
// That module has no dependency on other modules.
|
||||
func Example_oneModule() {
|
||||
// in this example we are testing the integration of the auth module:
|
||||
encodingCfg := moduletestutil.MakeTestEncodingConfig(auth.AppModuleBasic{})
|
||||
keys := storetypes.NewKVStoreKeys(authtypes.StoreKey)
|
||||
authority := authtypes.NewModuleAddress("gov").String()
|
||||
|
||||
accountKeeper := authkeeper.NewAccountKeeper(
|
||||
encodingCfg.Codec,
|
||||
runtime.NewKVStoreService(keys[authtypes.StoreKey]),
|
||||
authtypes.ProtoBaseAccount,
|
||||
map[string][]string{minttypes.ModuleName: {authtypes.Minter}},
|
||||
"cosmos",
|
||||
authority,
|
||||
)
|
||||
|
||||
// subspace is nil because we don't test params (which is legacy anyway)
|
||||
authModule := auth.NewAppModule(encodingCfg.Codec, accountKeeper, authsims.RandomGenesisAccounts, nil)
|
||||
|
||||
// create the application and register all the modules from the previous step
|
||||
// replace the name and the logger by testing values in a real test case (e.g. t.Name() and log.NewTestLogger(t))
|
||||
integrationApp := integration.NewIntegrationApp("example-one-module", log.NewLogger(io.Discard), keys, authModule)
|
||||
|
||||
// register the message and query servers
|
||||
authtypes.RegisterMsgServer(integrationApp.MsgServiceRouter(), authkeeper.NewMsgServerImpl(accountKeeper))
|
||||
|
||||
params := authtypes.DefaultParams()
|
||||
params.MaxMemoCharacters = 1000
|
||||
|
||||
// now we can use the application to test a mint message
|
||||
result, err := integrationApp.RunMsg(&authtypes.MsgUpdateParams{
|
||||
Authority: authority,
|
||||
Params: params,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// in this example the result is an empty response, a nil check is enough
|
||||
// in other cases, it is recommended to check the result value.
|
||||
if result == nil {
|
||||
panic(fmt.Errorf("unexpected nil result"))
|
||||
}
|
||||
|
||||
// we now check the result
|
||||
resp := authtypes.MsgUpdateParamsResponse{}
|
||||
err = encodingCfg.Codec.Unmarshal(result.Value, &resp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// we should also check the state of the application
|
||||
got := accountKeeper.GetParams(integrationApp.SDKContext())
|
||||
if diff := cmp.Diff(got, params); diff != "" {
|
||||
panic(diff)
|
||||
}
|
||||
fmt.Println(got.MaxMemoCharacters)
|
||||
// Output: 1000
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cometbft/cometbft/abci/types"
|
||||
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
storetypes "cosmossdk.io/store/types"
|
||||
dbm "github.com/cosmos/cosmos-db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/module"
|
||||
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
||||
)
|
||||
|
||||
// App is a test application that can be used to test the integration of modules.
|
||||
type App struct {
|
||||
*baseapp.BaseApp
|
||||
|
||||
ctx sdk.Context
|
||||
logger log.Logger
|
||||
|
||||
queryHelper *baseapp.QueryServiceTestHelper
|
||||
}
|
||||
|
||||
// NewIntegrationApp creates an application for testing purposes. This application is able to route messages to their respective handlers.
|
||||
func NewIntegrationApp(nameSuffix string, logger log.Logger, keys map[string]*storetypes.KVStoreKey, modules ...module.AppModuleBasic) *App {
|
||||
db := dbm.NewMemDB()
|
||||
|
||||
interfaceRegistry := codectypes.NewInterfaceRegistry()
|
||||
for _, module := range modules {
|
||||
module.RegisterInterfaces(interfaceRegistry)
|
||||
}
|
||||
|
||||
txConfig := authtx.NewTxConfig(codec.NewProtoCodec(interfaceRegistry), authtx.DefaultSignModes)
|
||||
|
||||
bApp := baseapp.NewBaseApp(fmt.Sprintf("integration-app-%s", nameSuffix), logger, db, txConfig.TxDecoder())
|
||||
bApp.MountKVStores(keys)
|
||||
bApp.SetInitChainer(func(ctx sdk.Context, req types.RequestInitChain) (types.ResponseInitChain, error) {
|
||||
return types.ResponseInitChain{}, nil
|
||||
})
|
||||
|
||||
router := baseapp.NewMsgServiceRouter()
|
||||
router.SetInterfaceRegistry(interfaceRegistry)
|
||||
bApp.SetMsgServiceRouter(router)
|
||||
|
||||
if err := bApp.LoadLatestVersion(); err != nil {
|
||||
panic(fmt.Errorf("failed to load application version from store: %w", err))
|
||||
}
|
||||
|
||||
ctx := bApp.NewContext(true, cmtproto.Header{})
|
||||
|
||||
return &App{
|
||||
BaseApp: bApp,
|
||||
|
||||
logger: logger,
|
||||
ctx: ctx,
|
||||
queryHelper: baseapp.NewQueryServerTestHelper(ctx, interfaceRegistry),
|
||||
}
|
||||
}
|
||||
|
||||
// RunMsg allows to run a message and return the response.
|
||||
// In order to run a message, the application must have a handler for it.
|
||||
// These handlers are registered on the application message service router.
|
||||
// The result of the message execution is returned as a Any type.
|
||||
// That any type can be unmarshaled to the expected response type.
|
||||
// If the message execution fails, an error is returned.
|
||||
func (app *App) RunMsg(msg sdk.Msg) (*codectypes.Any, error) {
|
||||
app.logger.Info("Running msg", "msg", msg.String())
|
||||
|
||||
handler := app.MsgServiceRouter().Handler(msg)
|
||||
if handler == nil {
|
||||
return nil, fmt.Errorf("handler is nil, can't route message %s: %+v", sdk.MsgTypeURL(msg), msg)
|
||||
}
|
||||
|
||||
msgResult, err := handler(app.ctx, msg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute message %s: %w", sdk.MsgTypeURL(msg), err)
|
||||
}
|
||||
|
||||
var response *codectypes.Any
|
||||
if len(msgResult.MsgResponses) > 0 {
|
||||
msgResponse := msgResult.MsgResponses[0]
|
||||
if msgResponse == nil {
|
||||
return nil, fmt.Errorf("got nil msg response %s in message result: %s", sdk.MsgTypeURL(msg), msgResult.String())
|
||||
}
|
||||
|
||||
response = msgResponse
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (app *App) SDKContext() sdk.Context {
|
||||
return app.ctx
|
||||
}
|
||||
|
||||
func (app *App) QueryHelper() *baseapp.QueryServiceTestHelper {
|
||||
return app.queryHelper
|
||||
}
|
||||
Reference in New Issue
Block a user