Add Tests and Mocks To Asset Transfer Chaincode

Many users raise questions on how to test chaincode. It used
to be much easier with the old shim, as they could directly
use the old mock stub in the shim. Now that it no longer exists
the fabcar example can provide an example of how to test chaincode.

Also worth noting is our current directory structure of our Go
chaincodes prevents the creation of mocks due to import cycles.
This change also pushes the chaincode logic down into a `chaincode`
package.

Signed-off-by: Brett Logan <brett.t.logan@ibm.com>
This commit is contained in:
Brett Logan 2020-07-06 16:57:19 -04:00 committed by denyeart
parent a04db374aa
commit 426cdf1f98
6 changed files with 3466 additions and 2 deletions

View file

@ -14,7 +14,7 @@ import (
func main() {
assetChaincode, err := contractapi.NewChaincode(&chaincode.SmartContract{})
if err != nil {
log.Panicf("Error create asset-transfer-basic chaincode: %v", err)
log.Panicf("Error creating asset-transfer-basic chaincode: %v", err)
}
if err := assetChaincode.Start(); err != nil {

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,232 @@
// Code generated by counterfeiter. DO NOT EDIT.
package mocks
import (
"sync"
"github.com/hyperledger/fabric-protos-go/ledger/queryresult"
)
type StateQueryIterator struct {
CloseStub func() error
closeMutex sync.RWMutex
closeArgsForCall []struct {
}
closeReturns struct {
result1 error
}
closeReturnsOnCall map[int]struct {
result1 error
}
HasNextStub func() bool
hasNextMutex sync.RWMutex
hasNextArgsForCall []struct {
}
hasNextReturns struct {
result1 bool
}
hasNextReturnsOnCall map[int]struct {
result1 bool
}
NextStub func() (*queryresult.KV, error)
nextMutex sync.RWMutex
nextArgsForCall []struct {
}
nextReturns struct {
result1 *queryresult.KV
result2 error
}
nextReturnsOnCall map[int]struct {
result1 *queryresult.KV
result2 error
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *StateQueryIterator) Close() error {
fake.closeMutex.Lock()
ret, specificReturn := fake.closeReturnsOnCall[len(fake.closeArgsForCall)]
fake.closeArgsForCall = append(fake.closeArgsForCall, struct {
}{})
fake.recordInvocation("Close", []interface{}{})
fake.closeMutex.Unlock()
if fake.CloseStub != nil {
return fake.CloseStub()
}
if specificReturn {
return ret.result1
}
fakeReturns := fake.closeReturns
return fakeReturns.result1
}
func (fake *StateQueryIterator) CloseCallCount() int {
fake.closeMutex.RLock()
defer fake.closeMutex.RUnlock()
return len(fake.closeArgsForCall)
}
func (fake *StateQueryIterator) CloseCalls(stub func() error) {
fake.closeMutex.Lock()
defer fake.closeMutex.Unlock()
fake.CloseStub = stub
}
func (fake *StateQueryIterator) CloseReturns(result1 error) {
fake.closeMutex.Lock()
defer fake.closeMutex.Unlock()
fake.CloseStub = nil
fake.closeReturns = struct {
result1 error
}{result1}
}
func (fake *StateQueryIterator) CloseReturnsOnCall(i int, result1 error) {
fake.closeMutex.Lock()
defer fake.closeMutex.Unlock()
fake.CloseStub = nil
if fake.closeReturnsOnCall == nil {
fake.closeReturnsOnCall = make(map[int]struct {
result1 error
})
}
fake.closeReturnsOnCall[i] = struct {
result1 error
}{result1}
}
func (fake *StateQueryIterator) HasNext() bool {
fake.hasNextMutex.Lock()
ret, specificReturn := fake.hasNextReturnsOnCall[len(fake.hasNextArgsForCall)]
fake.hasNextArgsForCall = append(fake.hasNextArgsForCall, struct {
}{})
fake.recordInvocation("HasNext", []interface{}{})
fake.hasNextMutex.Unlock()
if fake.HasNextStub != nil {
return fake.HasNextStub()
}
if specificReturn {
return ret.result1
}
fakeReturns := fake.hasNextReturns
return fakeReturns.result1
}
func (fake *StateQueryIterator) HasNextCallCount() int {
fake.hasNextMutex.RLock()
defer fake.hasNextMutex.RUnlock()
return len(fake.hasNextArgsForCall)
}
func (fake *StateQueryIterator) HasNextCalls(stub func() bool) {
fake.hasNextMutex.Lock()
defer fake.hasNextMutex.Unlock()
fake.HasNextStub = stub
}
func (fake *StateQueryIterator) HasNextReturns(result1 bool) {
fake.hasNextMutex.Lock()
defer fake.hasNextMutex.Unlock()
fake.HasNextStub = nil
fake.hasNextReturns = struct {
result1 bool
}{result1}
}
func (fake *StateQueryIterator) HasNextReturnsOnCall(i int, result1 bool) {
fake.hasNextMutex.Lock()
defer fake.hasNextMutex.Unlock()
fake.HasNextStub = nil
if fake.hasNextReturnsOnCall == nil {
fake.hasNextReturnsOnCall = make(map[int]struct {
result1 bool
})
}
fake.hasNextReturnsOnCall[i] = struct {
result1 bool
}{result1}
}
func (fake *StateQueryIterator) Next() (*queryresult.KV, error) {
fake.nextMutex.Lock()
ret, specificReturn := fake.nextReturnsOnCall[len(fake.nextArgsForCall)]
fake.nextArgsForCall = append(fake.nextArgsForCall, struct {
}{})
fake.recordInvocation("Next", []interface{}{})
fake.nextMutex.Unlock()
if fake.NextStub != nil {
return fake.NextStub()
}
if specificReturn {
return ret.result1, ret.result2
}
fakeReturns := fake.nextReturns
return fakeReturns.result1, fakeReturns.result2
}
func (fake *StateQueryIterator) NextCallCount() int {
fake.nextMutex.RLock()
defer fake.nextMutex.RUnlock()
return len(fake.nextArgsForCall)
}
func (fake *StateQueryIterator) NextCalls(stub func() (*queryresult.KV, error)) {
fake.nextMutex.Lock()
defer fake.nextMutex.Unlock()
fake.NextStub = stub
}
func (fake *StateQueryIterator) NextReturns(result1 *queryresult.KV, result2 error) {
fake.nextMutex.Lock()
defer fake.nextMutex.Unlock()
fake.NextStub = nil
fake.nextReturns = struct {
result1 *queryresult.KV
result2 error
}{result1, result2}
}
func (fake *StateQueryIterator) NextReturnsOnCall(i int, result1 *queryresult.KV, result2 error) {
fake.nextMutex.Lock()
defer fake.nextMutex.Unlock()
fake.NextStub = nil
if fake.nextReturnsOnCall == nil {
fake.nextReturnsOnCall = make(map[int]struct {
result1 *queryresult.KV
result2 error
})
}
fake.nextReturnsOnCall[i] = struct {
result1 *queryresult.KV
result2 error
}{result1, result2}
}
func (fake *StateQueryIterator) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.closeMutex.RLock()
defer fake.closeMutex.RUnlock()
fake.hasNextMutex.RLock()
defer fake.hasNextMutex.RUnlock()
fake.nextMutex.RLock()
defer fake.nextMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *StateQueryIterator) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}

View file

@ -0,0 +1,164 @@
// Code generated by counterfeiter. DO NOT EDIT.
package mocks
import (
"sync"
"github.com/hyperledger/fabric-chaincode-go/pkg/cid"
"github.com/hyperledger/fabric-chaincode-go/shim"
)
type TransactionContext struct {
GetClientIdentityStub func() cid.ClientIdentity
getClientIdentityMutex sync.RWMutex
getClientIdentityArgsForCall []struct {
}
getClientIdentityReturns struct {
result1 cid.ClientIdentity
}
getClientIdentityReturnsOnCall map[int]struct {
result1 cid.ClientIdentity
}
GetStubStub func() shim.ChaincodeStubInterface
getStubMutex sync.RWMutex
getStubArgsForCall []struct {
}
getStubReturns struct {
result1 shim.ChaincodeStubInterface
}
getStubReturnsOnCall map[int]struct {
result1 shim.ChaincodeStubInterface
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *TransactionContext) GetClientIdentity() cid.ClientIdentity {
fake.getClientIdentityMutex.Lock()
ret, specificReturn := fake.getClientIdentityReturnsOnCall[len(fake.getClientIdentityArgsForCall)]
fake.getClientIdentityArgsForCall = append(fake.getClientIdentityArgsForCall, struct {
}{})
fake.recordInvocation("GetClientIdentity", []interface{}{})
fake.getClientIdentityMutex.Unlock()
if fake.GetClientIdentityStub != nil {
return fake.GetClientIdentityStub()
}
if specificReturn {
return ret.result1
}
fakeReturns := fake.getClientIdentityReturns
return fakeReturns.result1
}
func (fake *TransactionContext) GetClientIdentityCallCount() int {
fake.getClientIdentityMutex.RLock()
defer fake.getClientIdentityMutex.RUnlock()
return len(fake.getClientIdentityArgsForCall)
}
func (fake *TransactionContext) GetClientIdentityCalls(stub func() cid.ClientIdentity) {
fake.getClientIdentityMutex.Lock()
defer fake.getClientIdentityMutex.Unlock()
fake.GetClientIdentityStub = stub
}
func (fake *TransactionContext) GetClientIdentityReturns(result1 cid.ClientIdentity) {
fake.getClientIdentityMutex.Lock()
defer fake.getClientIdentityMutex.Unlock()
fake.GetClientIdentityStub = nil
fake.getClientIdentityReturns = struct {
result1 cid.ClientIdentity
}{result1}
}
func (fake *TransactionContext) GetClientIdentityReturnsOnCall(i int, result1 cid.ClientIdentity) {
fake.getClientIdentityMutex.Lock()
defer fake.getClientIdentityMutex.Unlock()
fake.GetClientIdentityStub = nil
if fake.getClientIdentityReturnsOnCall == nil {
fake.getClientIdentityReturnsOnCall = make(map[int]struct {
result1 cid.ClientIdentity
})
}
fake.getClientIdentityReturnsOnCall[i] = struct {
result1 cid.ClientIdentity
}{result1}
}
func (fake *TransactionContext) GetStub() shim.ChaincodeStubInterface {
fake.getStubMutex.Lock()
ret, specificReturn := fake.getStubReturnsOnCall[len(fake.getStubArgsForCall)]
fake.getStubArgsForCall = append(fake.getStubArgsForCall, struct {
}{})
fake.recordInvocation("GetStub", []interface{}{})
fake.getStubMutex.Unlock()
if fake.GetStubStub != nil {
return fake.GetStubStub()
}
if specificReturn {
return ret.result1
}
fakeReturns := fake.getStubReturns
return fakeReturns.result1
}
func (fake *TransactionContext) GetStubCallCount() int {
fake.getStubMutex.RLock()
defer fake.getStubMutex.RUnlock()
return len(fake.getStubArgsForCall)
}
func (fake *TransactionContext) GetStubCalls(stub func() shim.ChaincodeStubInterface) {
fake.getStubMutex.Lock()
defer fake.getStubMutex.Unlock()
fake.GetStubStub = stub
}
func (fake *TransactionContext) GetStubReturns(result1 shim.ChaincodeStubInterface) {
fake.getStubMutex.Lock()
defer fake.getStubMutex.Unlock()
fake.GetStubStub = nil
fake.getStubReturns = struct {
result1 shim.ChaincodeStubInterface
}{result1}
}
func (fake *TransactionContext) GetStubReturnsOnCall(i int, result1 shim.ChaincodeStubInterface) {
fake.getStubMutex.Lock()
defer fake.getStubMutex.Unlock()
fake.GetStubStub = nil
if fake.getStubReturnsOnCall == nil {
fake.getStubReturnsOnCall = make(map[int]struct {
result1 shim.ChaincodeStubInterface
})
}
fake.getStubReturnsOnCall[i] = struct {
result1 shim.ChaincodeStubInterface
}{result1}
}
func (fake *TransactionContext) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.getClientIdentityMutex.RLock()
defer fake.getClientIdentityMutex.RUnlock()
fake.getStubMutex.RLock()
defer fake.getStubMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *TransactionContext) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}

View file

@ -0,0 +1,184 @@
package chaincode_test
import (
"encoding/json"
"fmt"
"testing"
"github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
"github.com/hyperledger/fabric-protos-go/ledger/queryresult"
"github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-go/chaincode"
"github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-go/chaincode/mocks"
"github.com/stretchr/testify/require"
)
//go:generate counterfeiter -o mocks/transaction.go -fake-name TransactionContext . transactionContext
type transactionContext interface {
contractapi.TransactionContextInterface
}
//go:generate counterfeiter -o mocks/chaincodestub.go -fake-name ChaincodeStub . chaincodeStub
type chaincodeStub interface {
shim.ChaincodeStubInterface
}
//go:generate counterfeiter -o mocks/statequeryiterator.go -fake-name StateQueryIterator . stateQueryIterator
type stateQueryIterator interface {
shim.StateQueryIteratorInterface
}
func TestInitLedger(t *testing.T) {
chaincodeStub := &mocks.ChaincodeStub{}
transactionContext := &mocks.TransactionContext{}
transactionContext.GetStubReturns(chaincodeStub)
assetTransfer := chaincode.SmartContract{}
err := assetTransfer.InitLedger(transactionContext)
require.NoError(t, err)
chaincodeStub.PutStateReturns(fmt.Errorf("failed inserting key"))
err = assetTransfer.InitLedger(transactionContext)
require.EqualError(t, err, "failed to put to world state. failed inserting key")
}
func TestCreateAsset(t *testing.T) {
chaincodeStub := &mocks.ChaincodeStub{}
transactionContext := &mocks.TransactionContext{}
transactionContext.GetStubReturns(chaincodeStub)
assetTransfer := chaincode.SmartContract{}
err := assetTransfer.CreateAsset(transactionContext, "", "", "", 0, 0)
require.NoError(t, err)
chaincodeStub.GetStateReturns([]byte{}, nil)
err = assetTransfer.CreateAsset(transactionContext, "asset1", "", "", 0, 0)
require.EqualError(t, err, "the asset asset1 already exists")
chaincodeStub.GetStateReturns(nil, fmt.Errorf("unable to retrieve asset"))
err = assetTransfer.CreateAsset(transactionContext, "asset1", "", "", 0, 0)
require.EqualError(t, err, "failed to read from world state: unable to retrieve asset")
}
func TestReadAsset(t *testing.T) {
chaincodeStub := &mocks.ChaincodeStub{}
transactionContext := &mocks.TransactionContext{}
transactionContext.GetStubReturns(chaincodeStub)
expectedAsset := &chaincode.Asset{ID: "asset1"}
bytes, err := json.Marshal(expectedAsset)
require.NoError(t, err)
chaincodeStub.GetStateReturns(bytes, nil)
assetTransfer := chaincode.SmartContract{}
asset, err := assetTransfer.ReadAsset(transactionContext, "")
require.NoError(t, err)
require.Equal(t, expectedAsset, asset)
chaincodeStub.GetStateReturns(nil, fmt.Errorf("unable to retrieve asset"))
_, err = assetTransfer.ReadAsset(transactionContext, "")
require.EqualError(t, err, "failed to read from world state: unable to retrieve asset")
chaincodeStub.GetStateReturns(nil, nil)
asset, err = assetTransfer.ReadAsset(transactionContext, "asset1")
require.EqualError(t, err, "the asset asset1 does not exist")
require.Nil(t, asset)
}
func TestUpdateAsset(t *testing.T) {
chaincodeStub := &mocks.ChaincodeStub{}
transactionContext := &mocks.TransactionContext{}
transactionContext.GetStubReturns(chaincodeStub)
expectedAsset := &chaincode.Asset{ID: "asset1"}
bytes, err := json.Marshal(expectedAsset)
require.NoError(t, err)
chaincodeStub.GetStateReturns(bytes, nil)
assetTransfer := chaincode.SmartContract{}
err = assetTransfer.UpdateAsset(transactionContext, "", "", "", 0, 0)
require.NoError(t, err)
chaincodeStub.GetStateReturns(nil, nil)
err = assetTransfer.UpdateAsset(transactionContext, "asset1", "", "", 0, 0)
require.EqualError(t, err, "the asset asset1 does not exist")
chaincodeStub.GetStateReturns(nil, fmt.Errorf("unable to retrieve asset"))
err = assetTransfer.UpdateAsset(transactionContext, "asset1", "", "", 0, 0)
require.EqualError(t, err, "failed to read from world state: unable to retrieve asset")
}
func TestDeleteAsset(t *testing.T) {
chaincodeStub := &mocks.ChaincodeStub{}
transactionContext := &mocks.TransactionContext{}
transactionContext.GetStubReturns(chaincodeStub)
asset := &chaincode.Asset{ID: "asset1"}
bytes, err := json.Marshal(asset)
require.NoError(t, err)
chaincodeStub.GetStateReturns(bytes, nil)
chaincodeStub.DelStateReturns(nil)
assetTransfer := chaincode.SmartContract{}
err = assetTransfer.DeleteAsset(transactionContext, "")
require.NoError(t, err)
chaincodeStub.GetStateReturns(nil, nil)
err = assetTransfer.DeleteAsset(transactionContext, "asset1")
require.EqualError(t, err, "the asset asset1 does not exist")
chaincodeStub.GetStateReturns(nil, fmt.Errorf("unable to retrieve asset"))
err = assetTransfer.DeleteAsset(transactionContext, "")
require.EqualError(t, err, "failed to read from world state: unable to retrieve asset")
}
func TestTransferAsset(t *testing.T) {
chaincodeStub := &mocks.ChaincodeStub{}
transactionContext := &mocks.TransactionContext{}
transactionContext.GetStubReturns(chaincodeStub)
asset := &chaincode.Asset{ID: "asset1"}
bytes, err := json.Marshal(asset)
require.NoError(t, err)
chaincodeStub.GetStateReturns(bytes, nil)
assetTransfer := chaincode.SmartContract{}
err = assetTransfer.TransferAsset(transactionContext, "", "")
require.NoError(t, err)
chaincodeStub.GetStateReturns(nil, fmt.Errorf("unable to retrieve asset"))
err = assetTransfer.TransferAsset(transactionContext, "", "")
require.EqualError(t, err, "failed to read from world state: unable to retrieve asset")
}
func TestGetAllAssets(t *testing.T) {
asset := &chaincode.Asset{ID: "asset1"}
bytes, err := json.Marshal(asset)
require.NoError(t, err)
iterator := &mocks.StateQueryIterator{}
iterator.HasNextReturnsOnCall(0, true)
iterator.HasNextReturnsOnCall(1, false)
iterator.NextReturns(&queryresult.KV{Value: bytes}, nil)
chaincodeStub := &mocks.ChaincodeStub{}
transactionContext := &mocks.TransactionContext{}
transactionContext.GetStubReturns(chaincodeStub)
chaincodeStub.GetStateByRangeReturns(iterator, nil)
assetTransfer := &chaincode.SmartContract{}
assets, err := assetTransfer.GetAllAssets(transactionContext)
require.NoError(t, err)
require.Equal(t, []chaincode.QueryResult{{Record: asset}}, assets)
iterator.HasNextReturns(true)
iterator.NextReturns(nil, fmt.Errorf("failed retrieving next item"))
assets, err = assetTransfer.GetAllAssets(transactionContext)
require.EqualError(t, err, "failed retrieving next item")
require.Nil(t, assets)
chaincodeStub.GetStateByRangeReturns(nil, fmt.Errorf("failed retrieving all assets"))
assets, err = assetTransfer.GetAllAssets(transactionContext)
require.EqualError(t, err, "failed retrieving all assets")
require.Nil(t, assets)
}

View file

@ -2,4 +2,10 @@ module github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-go
go 1.14
require github.com/hyperledger/fabric-contract-api-go v1.1.0
require (
github.com/golang/protobuf v1.3.2
github.com/hyperledger/fabric-chaincode-go v0.0.0-20200424173110-d7076418f212
github.com/hyperledger/fabric-contract-api-go v1.1.0
github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e
github.com/stretchr/testify v1.5.1
)