github.com/s7techlab/cckit@v0.10.5/examples/insurance/README.md (about) 1 # Tests for IBM Blockchain insurance app chaincodes with CCKit 2 3 This example shows how to apply concepts of test-driven development to writing chaincode in Golang for Hyperledger Fabric 1.2 4 using shim MockStub to unit-test your chaincode without having to deploy it in a Blockchain network. 5 The use of described testing technique allowed us to find small bug in 6 [IBM blockchain insurance app](https://github.com/IBM/build-blockchain-insurance-app/pull/44) 7 8 9 ## IBM blockchain insurance app 10 11 Blockchain insurance application example https://developer.ibm.com/code/patterns/build-a-blockchain-insurance-app/ 12 shows how through distributed ledger and smart contracts blockchain can transform insurance processes: 13 14 >Blockchain presents a huge opportunity for the insurance industry. It offers the chance to innovate around the way 15 data is exchanged, claims are processed, and fraud is prevented. Blockchain can bring together developers from tech 16 companies, regulators, and insurance companies to create a valuable new insurance management asset. 17 18 ![Architecture](app/images/arch-blockchain-insurance2.png) 19 20 21 ### Source code 22 23 https://github.com/IBM/build-blockchain-insurance-app 24 25 Chaincode from https://github.com/IBM/build-blockchain-insurance-app/tree/master/web/chaincode/src/bcins is copied to 26 the [app](app) directory for sample test creation. 27 28 ## Test-driven development 29 30 Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle: 31 requirements are turned into very specific test cases, then the software is improved to pass the new tests, 32 only. This is opposed to software development that allows software to be added that is not proven to meet requirements. 33 34 When creating smart contracts on the Ethereum platform or chaincodes for Hyperledger Fabric, the developer programs a certain work logic 35 that determines how the methods should change the state of the smart contract / chaincodes , what events are to be emitted, 36 when to return success or error response. This kind of software is ideal for development through testing. 37 38 39 ## Why test with Mockstub 40 41 Creating blockchain network and deploying the chaincode(s) to it is quite cumbersome and slow process, especially if your 42 code is constantly changing during developing process. 43 44 The Hyperledger Fabric [shim package](https://github.com/hyperledger/fabric/tree/release-1.2/core/chaincode/shim) 45 contains the [MockStub](https://github.com/hyperledger/fabric/blob/release-1.2/core/chaincode/shim/mockstub.go) 46 implementation that wraps chaincode and stub out the calls to 47 [shim.ChaincodeStubInterface](https://github.com/hyperledger/fabric/blob/release-1.2/core/chaincode/shim/interfaces.go) 48 in chaincode functions. Mockstub allows you to get the test results almost immediately. 49 50 51 MockStub contains implementation for most of `shim.ChaincodeStubInterface` function, but in the current version 52 of Hyperledger Fabric (1.4), the MockStub has not implemented some of the important methods such 53 as `GetCreator`, for example. Since chaincode would use this method to get tx creator certificate 54 for access control, it's critical to be able to stub this method in order to completely unit-test chaincode. 55 56 `CCKit` contains extended [MockStub](../../testing/mockstub.go) with implementation of some of the unimplemented 57 methods and delegating existing ones to shim.MockStub. [CCkit MockStub]((../../testing/mockstub.go)) holds a reference 58 to the original MockStub and has additional methods and properties. 59 60 61 62 ## Tests for this application 63 64 The tests are located in [current](.) directory: 65 66 * [app_test.go](app_test.go) contains [Ginkgo](https://onsi.github.io/ginkgo/) based tests 67 * [dto.go](dto.go) contains named DTO (data transfer objects) from original chaincode source code 68 * [fixture.go](fixtures.go) contains fixtures for tests 69 70 71 ## Getting started 72 73 Before you begin, be sure to get `CCkit`: 74 75 `git clone git@github.com:s7techlab/cckit.git` 76 77 and get dependencies using `go mod` command: 78 79 `go mod vendor` 80 81 82 CCkit repository contains several examples: 83 84 * [Cars](../cars) 85 * [Marbles](../marbles) 86 * And `insurance` 87 88 All examples and this tutorial uses the [Ginkgo](https://onsi.github.io/ginkgo/) testing library, it hooks into Go’s existing testing infrastructure. This allows you to run a Ginkgo suite using go test. 89 We import the ginkgo and gomega packages into the test’s top-level namespace by performing a dot-import. More about 90 testing with Ginkgo you can read on [onsi.github.io/ginkgo/](https://onsi.github.io/ginkgo/) 91 92 93 ## Creating test suite 94 95 Using separate [package with tests](.) instead of [chaincode package](app) allows us to respect 96 the encapsulation of the chaincode package: your tests will need to import chaincode and access it from the outside. 97 98 Test suite bootstrap example: 99 100 ```go 101 package main 102 103 import ( 104 "fmt" 105 "testing" 106 "github.com/s7techlab/cckit/examples/insurance/app" 107 . "github.com/onsi/ginkgo" 108 . "github.com/onsi/gomega" 109 testcc "github.com/s7techlab/cckit/testing" 110 expectcc "github.com/s7techlab/cckit/testing/expect" 111 ) 112 113 func TestInsuranceApp(t *testing.T) { 114 RegisterFailHandler(Fail) 115 RunSpecs(t, "Insurance app suite") 116 } 117 118 var _ = Describe(`Insurance`, func() { 119 120 //Create chaincode mock 121 cc := testcc.NewMockStub(`insurance`, new(app.SmartContract)) 122 123 ... 124 } 125 } 126 ``` 127 128 129 ## What to test 130 131 Chaincode interface functions described in file [main.go](main.go), so we can see all possible operations with chaincode data: 132 133 * list contract types 134 * create contract type 135 * activate contract type 136 * list contracts 137 * list claims... 138 139 ```go 140 var bcFunctions = map[string]func(shim.ChaincodeStubInterface, []string) pb.Response{ 141 // Insurance Peer 142 "contract_type_ls": listContractTypes, 143 "contract_type_create": createContractType, 144 "contract_type_set_active": setActiveContractType, 145 "contract_ls": listContracts, 146 "claim_ls": listClaims, 147 "claim_file": fileClaim, 148 "claim_process": processClaim, 149 "user_authenticate": authUser, 150 "user_get_info": getUser, 151 // Shop Peer 152 "contract_create": createContract, 153 "user_create": createUser, 154 // Repair Shop Peer 155 "repair_order_ls": listRepairOrders, 156 "repair_order_complete": completeRepairOrder, 157 // Police Peer 158 "theft_claim_ls": listTheftClaims, 159 "theft_claim_process": processTheftClaim, 160 } 161 ``` 162 Also chaincode has `init` function. So we can use them for testing insurance chaincode functionality. 163 164 165 ## Data transfer objects and fixtures 166 167 In this example we use [Data Transfer Objects](dto.go) (DTO) for creating chaincode args and 168 [fixtures](fixtures.go) 169 170 171 ## Test init function 172 173 ```go 174 Describe("Chaincode initialization ", func() { 175 It("Allow to provide contract types attributes during chaincode creation [init]", func() { 176 expectcc.ResponseOk(cc.Init(`init`, &ContractTypesDTO{ContractType1})) 177 }) 178 }) 179 ```` 180 181 ## Test operations with contract types 182 183 ```go 184 Describe("Contract type ", func() { 185 186 It("Allow to retrieve all contract type, added during chaincode init [contract_type_ls]", func() { 187 contractTypes := expectcc.PayloadIs( 188 cc.Invoke(`contract_type_ls`), &ContractTypesDTO{}).(ContractTypesDTO) 189 190 Expect(len(contractTypes)).To(Equal(1)) 191 Expect(contractTypes[0].ShopType).To(Equal(ContractType1.ShopType)) 192 }) 193 194 It("Allow to create new contract type [contract_type_create]", func() { 195 expectcc.ResponseOk(cc.Invoke(`contract_type_create`, &ContractType2)) 196 197 // get contract type list 198 contractTypes := expectcc.PayloadIs( 199 cc.Invoke(`contract_type_ls`), &ContractTypesDTO{}).(ContractTypesDTO) 200 201 //expect now we have 2 contract type 202 Expect(len(contractTypes)).To(Equal(2)) 203 }) 204 205 It("Allow to set active contract type [contract_type_set_active]", func() { 206 Expect(ContractType2.Active).To(BeFalse()) 207 208 // active ContractType2 209 expectcc.ResponseOk(cc.Invoke(`contract_type_set_active`, &ContractTypeActiveDTO{ 210 UUID: ContractType2.UUID, Active: true})) 211 }) 212 213 It("Allow to retrieve filtered by shop type contract types [contract_type_ls]", func() { 214 contractTypes := expectcc.PayloadIs( 215 cc.Invoke(`contract_type_ls`, &ShopTypeDTO{ContractType2.ShopType}), 216 &ContractTypesDTO{}).(ContractTypesDTO) 217 218 Expect(len(contractTypes)).To(Equal(1)) 219 Expect(contractTypes[0].UUID).To(Equal(ContractType2.UUID)) 220 221 // Contract type 2 activated on previous step 222 Expect(contractTypes[0].Active).To(BeTrue()) 223 }) 224 }) 225 ``` 226 227 ## Test operations with contracts 228 229 ```go 230 Describe("Contract", func() { 231 232 It("Allow everyone to create contract", func() { 233 234 // get chaincode invoke response payload and expect returned payload is serialized instance of some structure 235 contractCreateResponse := expectcc.PayloadIs( 236 cc.Invoke( 237 // invoke chaincode function 238 `contract_create`, 239 // with ContractDTO payload, it will automatically will be marshalled to json 240 &Contract1, 241 ), 242 // We expect than payload response is marshalled ContractCreateResponse structure 243 &ContractCreateResponse{}).(ContractCreateResponse) 244 245 Expect(contractCreateResponse.Username).To(Equal(Contract1.Username)) 246 }) 247 248 It("Allow every one to get user info", func() { 249 250 // orininally was error https://github.com/IBM/build-blockchain-insurance-app/pull/44 251 user := expectcc.PayloadIs( 252 cc.Invoke(`user_get_info`, &GetUserDTO{ 253 Username: Contract1.Username, 254 }), &ResponseUserDTO{}).(ResponseUserDTO) 255 256 Expect(user.LastName).To(Equal(Contract1.LastName)) 257 }) 258 259 }) 260 ```