I'm writing unit-tests to cover all execution paths in the fabric chaincode below. But I don't see a way to reach the JSON.Marshall failing path.
How can I pass a value that passes to json.Unmarshal
but it fails to json.Marshal
?
func (c *MyContract) CreateAsset(ctx contractapi.TransactionContextInterface, values string) (bool, error) {
doctype := "myAsset"
txData := []byte(values)
docData := new(DocData)
docData.DataType = doctype
// validate json input & map to struct
err := json.Unmarshal(txData, &docData)
if err != nil {
return false, fmt.Errorf("failed docData unmarshalling: %s", err.Error())
}
docKey, _ := createKey(ctx, doctype, []string{docData.Key1, docData.Key2})
exists, err := c.DocExists(ctx, docKey)
if err != nil {
return false, fmt.Errorf("could not read from world state %s", err)
} else if exists {
return false, fmt.Errorf("asset already exists")
}
txBytes, err := json.Marshal(docData)
if err != nil {
return false, fmt.Errorf("failed docData bytes marshalling: %s", err.Error())
}
return true, ctx.GetStub().PutState(docKey, txBytes)
}
This is my current test:
func TestCreateAsset(t *testing.T) {
var err error
ctx, _ := setupStub()
c := new(MyContract)
_, err = c.CreateAsset(ctx, "{")
assert.EqualError(t, err, "failed unmarshalling: unexpected end of JSON input", "testing malformed json")
_, err = c.CreateAsset(ctx, "{\"key1\":\"mis\",\"key2\":\"sing\"}")
assert.EqualError(t, err, "could not read from world state some failure")
_, err = c.CreateAsset(ctx, "{\"key1\":\"001\",\"key2\":\"002\"}")
assert.EqualError(t, err, "asset already exists")
}
CodePudding user response:
The json.Marshal
function fails when a value in the data cannot be marshaled. The only way to induce a failure is to introduce a field for testing and slip in a bad value during a test:
type DocData struct {
…
Test interface{} `json:"test,omitempty"`
}
…
var induceFailure interface{}
…
docData.Test = induceFailure
txBytes, err := json.Marshal(docData)
if err != nil {
return false, fmt.Errorf("failed docData bytes marshalling: %s", err.Error())
}
…
func TestMarshlFail(t *testing.T) {
induceFailure = make(chan struct{})
defer func() {
induceFailure= nil
}()
…
It's probably not worth the hassle to get the test coverage on that line of code.
Unrelated to the question at hand, here are some improvements for your code.
Wrap errors instead of converting the errors to strings:
return nil, fmt.Errorf("failed docData unmarshalling: %w", err)
Because
docData
is a pointer, there's no need to take the address of the value when unmarshaling.err := json.Unmarshal(txData, docData)