Skip to content

Adding a unit test framework to scaffolded applications #715

@PaddyMc

Description

@PaddyMc

Description

To improve the code quality and allow a deeper understanding of what happening under the hood of cosmos-sdk powered applications, we could add a unit test framework to the starport scaffolding

Implementation Details

We should follow best practices when defining unit test frameworks, this usually means table-driven testing and using test suites.

A sample test suite for the keeper package could look like:

The keeper test suite will contain a context and other structures needed to run the keeper correctly

// Keeper test suit enables the keeper package to be tested
type KeeperTestSuite struct {
	suite.Suite

	ctx         sdk.Context
	keeper      Keeper
	queryClient types.QueryClient
	msgServer   types.MsgServer
}

The suite will be initialized by calling the SetupSuite() function which could look as follows: NOTE: we could initialise simapp here if we need some of the default keepers

// SetupTest creates a test suite to test the identifier
func (suite *KeeperTestSuite) SetupTest() {
	keyIdentifier := sdk.NewKVStoreKey(types.StoreKey)
	memKeyIdentifier := sdk.NewKVStoreKey(types.MemStoreKey)

	db := dbm.NewMemDB()
	ms := store.NewCommitMultiStore(db)
	ms.MountStoreWithDB(keyIdentifier, sdk.StoreTypeIAVL, db)
	ms.MountStoreWithDB(memKeyIdentifier, sdk.StoreTypeIAVL, db)
	_ = ms.LoadLatestVersion()

	ctx := sdk.NewContext(ms, tmproto.Header{ChainID: "foochainid"}, true, nil)

	interfaceRegistry := ct.NewInterfaceRegistry()
	marshaler := codec.NewProtoCodec(interfaceRegistry)

	k := NewKeeper(
		marshaler,
		keyIdentifier,
		memKeyIdentifier,
	)

	queryHelper := baseapp.NewQueryServerTestHelper(ctx, interfaceRegistry)
	types.RegisterQueryServer(queryHelper, k)
	queryClient := types.NewQueryClient(queryHelper)

	msgServer := NewMsgServerImpl(*k) <== this doesn't really work atm, sorry for adding it here

	suite.ctx, suite.keeper, suite.queryClient, suite.msgServer = ctx, *k, queryClient, msgServer
}

Finally to run the full test suite we would call the RunKeeperTestSuite functions which would look as follows:

func TestKeeperTestSuite(t *testing.T) {
	suite.Run(t, new(KeeperTestSuite))
}

Then adding tests to the keeper package would look as follows

func (suite *KeeperTestSuite) TestGenericKeeperSetAndGet() {
	testCases := []struct {
		msg        string
		identifier types.DidDocument
		expPass    bool
	}{
		{
			"data stored successfully",
			types.DidDocument{
				"context",
				"did:cash:1111",
				nil,
				nil,
			},
			true,
		},
	}
	for _, tc := range testCases {
		suite.keeper.Set(suite.ctx,
			[]byte(tc.identifier.Id),
			[]byte{0x01},
			tc.identifier,
			suite.keeper.MarshalIdentifier,
		)
		suite.keeper.Set(suite.ctx,
			[]byte(tc.identifier.Id+"1"),
			[]byte{0x01},
			tc.identifier,
			suite.keeper.MarshalIdentifier,
		)
		suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
			if tc.expPass {
				_, found := suite.keeper.Get(
					suite.ctx,
					[]byte(tc.identifier.Id),
					[]byte{0x01},
					suite.keeper.UnmarshalIdentifier,
				)
				suite.Require().True(found)

				allEntities := suite.keeper.GetAll(
					suite.ctx,
					[]byte{0x01},
					suite.keeper.UnmarshalIdentifier,
				)
				suite.Require().Equal(2, len(allEntities))
			} else {
				// TODO write failure cases
				suite.Require().False(tc.expPass)
			}
		})
	}
}

😄

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions