github.com/s7techlab/cckit@v0.10.5/testing/README.md (about)

     1  # Test Driven Hyperledger Fabric Golang chaincode development 
     2  
     3  ## How to fast and continuously check smart contract logic : blockchain transactions, fired events and chaincode permissioning
     4  
     5  Testing stage is a critical requirement for software quality assurance, doesn't matter is this  
     6  web application or a smart contract. Tests must be fast enough to run on every commit to repository. 
     7  [CCKit](https://github.com/s7techlab/cckit/), programming toolkit for developing and testing Hyperledger Fabric Golang 
     8  chaincodes, enhances the development experience with extended version of MockStub for chaincode testing. 
     9  
    10  ### Steps in chaincode development process
    11  
    12  A smart contract defines the different states of a business object and governs the processes that move the object between 
    13  these different states. Smart contracts allows architects and smart contract developers to define the 
    14  business processes and structure of data that are shared across different organizations collaborating in a blockchain network.
    15  
    16  The job of a smart contract developer is to take an existing business process and express it as a smart contract in a 
    17  programming language. Steps of chaincode development:
    18      
    19  *    Define chaincode model - schema for state entries, input payload and events
    20  *    Define chaincode interface
    21  *    Implement chaincode instantiate method
    22  *    Implement chaincode methods with business logic
    23  *    Create tests
    24  
    25  Test driven development (TDD) or Behavioral Driven Development, possibly, single way to develop smart contracts.
    26  
    27  ### Chaincode (smart contract) testing 
    28  
    29  Tests must ensure that chaincode works as expected:
    30  
    31  * particular input payload leads to particular business object state change
    32  * particular (invalid) input payload leads to validation or other errors
    33  * particular object state allow subset of state transitions (state machine)
    34  
    35  Any software testing (chaincode or web application for example) may either be a manual or an automated process. Manual 
    36  software testing is led by a team or individual who will manually operate a software product and ensure it behaves as expected.
    37  In case of chaincode tests you can manually invoke chaincode via `peer` cli tools.
    38  
    39  Automated software testing is the practice of instrumenting input and output correctness checks for individual units of code.
    40  During automated testing, code are executed in a test environment with simulated input.
    41  
    42  ### Running chaincode
    43  
    44  Deploying chaincode to blockchain network isn't the quickest thing in the world, there's a lot of time that can be saved 
    45  with testing. Also, more importantly, since blockchain is immutable and supposed to be secure because the code is on the
    46  network, we rather not leave flaws in our code. 
    47             
    48  During chaincode development and deploying to live network we can divide testing to multiple stage - fast stage, when 
    49  testing only smart contract logic, and more complicated stage, when we do integration testing with live blockchain network,
    50  multiple peers, deployed on-chain code (smart contracts) and off-chain application, that uses SDK to connect with
    51  blockchain network peers. 
    52  
    53  ### Chaincode DEV mode
    54  
    55  Deploying a Hyperledger Fabric blockchain network, chaincode installing and initializing, is quite complicated to set up and
    56  a long procedure. Time to re-install / upgrade the code of a smart contract can be reduced by using 
    57  [chaincode dev mode](https://hyperledger-fabric.readthedocs.io/en/latest/peer-chaincode-devmode.html). Normally chaincodes 
    58  are started and maintained by peer. In “dev” mode, chaincode is built and started by the user. 
    59  This mode is useful during chaincode development phase for rapid code/build/run/debug cycle turnaround. However, the process 
    60  of updating the code will still be slow.
    61  
    62  ### MockStub - mocked chaincode stub
    63  
    64  Mocking is a unit testing phenomenon which helps to test objects in isolation by replacing dependent objects with 
    65  complex behavior with test objects with pre-defined/simulated behavior. These test objects are called as Mock objects.
    66  
    67  The [shim](https://github.com/hyperledger/fabric/tree/master/core/chaincode/shim) package contains a 
    68  [MockStub](https://github.com/hyperledger/fabric/tree/master/core/chaincode/shim/mockstub.go) implementation 
    69  that wraps calls to a chaincode, simulating its behavior in the HLF peer environment. MockStub does not need to start 
    70  multiple docker containers with peer, world state database, chaincodes and allows to get test results almost immediately.
    71  MockStub essentially replaces the SDK and peer environment and allows to test chaincode without actually starting your 
    72  blockchain network. It implements almost every function the actual stub does, but in memory.
    73  
    74  ![mockstub](../docs/img/mockstub-hlf-peer.png)
    75  
    76  `MockStub` from [https://github.com/hyperledger/fabric/](https://github.com/hyperledger/fabric/) repository includes 
    77  implementation for most of `shim.ChaincodeStubInterface` function, but until current version 
    78  of Hyperledger Fabric (1.4), the `MockStub` has not implemented some of the important methods such
    79  as `GetCreator` or method for work with private state range, for example. Since chaincode would use `GetCreator`  method 
    80  to get transaction creator certificate for access control, it's critical to be able to stub this method in order 
    81  to completely unit-test chaincode.
    82  
    83  ### CCKit MockStub
    84  
    85  CCKit [testing](.) package contains:
    86  
    87  * [MockStub](mockstub.go) with implemented `GetTransient` and others methods and event subscription feature
    88  * Test [identity](identity.go) creation helpers
    89  * Chaincode response [expect](expect) helpers
    90  
    91  
    92  # Example: `Commercial Paper` chaincode
    93  
    94  ## Scenario
    95  
    96  Official hyperledger fabric documentation contain detailed 
    97  [chaincode example](https://hyperledger-fabric.readthedocs.io/en/release-1.4/developapps/scenario.html) -  `Commercial Paper`
    98  smart contract that defines the valid states for commercial paper, and the transaction logic that transition 
    99  a paper from one state to another. We will test [commercial paper extended chaincode example](../examples/cpaper_extended) 
   100  based on CCKit with protobuf state, described in this 
   101  [article](https://medium.com/coinmonks/hyperledger-fabric-smart-contract-data-model-protobuf-to-chaincode-state-mapping-191cdcfa0b78).
   102  
   103  We can represent the lifecycle of a commercial paper using a state transition diagram: commercial papers transition 
   104  between issued, trading and redeemed states by means of the issue, buy and redeem transactions.
   105  
   106  ![state](../examples/cpaper/img/state.png)
   107  
   108  ## Requirements 
   109  
   110  To produce tests first we need to define requirements to tested application. Let’s start by listing our requirements 
   111  for commercial paper chaincode:
   112  
   113  * It should allow the issuer to issue commercial paper
   114  * It should allow the participant to buy commercial paper
   115  * It should allow the owner to redeem commercial paper
   116  
   117  Chaincode interface functions described in file [chaincode.go](../examples/cpaper_extended/chaincode.go), so we can see 
   118  all possible operations (transactions) with chaincode data:
   119  
   120  ```go 
   121      Query("list", queryCPapers).
   122  
   123      // Get method has 2 params - commercial paper primary key components
   124      Query("get", queryCPaper, defparam.Proto(&schema.CommercialPaperId{})).
   125      Query("getByExternalId", queryCPaperGetByExternalId, param.String("externalId")).
   126  
   127      // txn methods
   128      Invoke("issue", invokeCPaperIssue, defparam.Proto(&schema.IssueCommercialPaper{})).
   129      Invoke("buy", invokeCPaperBuy, defparam.Proto(&schema.BuyCommercialPaper{})).
   130      Invoke("redeem", invokeCPaperRedeem, defparam.Proto(&schema.RedeemCommercialPaper{})).
   131      Invoke("delete", invokeCPaperDelete, defparam.Proto(&schema.CommercialPaperId{}))
   132  
   133  ```
   134  
   135  ## Getting started
   136  
   137  Before you begin, be sure to get `CCKit`:
   138  
   139  `git clone git@github.com:s7techlab/cckit.git`
   140  
   141  This will fetch and install the CCKit package with [examples](../examples). After that we need to install the dependencies 
   142  using command:
   143  
   144  `go mod vendor`
   145  
   146  ## Creating test suite 
   147  
   148  ### Testing in Go
   149  
   150  Go has a built-in testing command called `go test` and a package `testing` which gives a minimal but complete 
   151  testing experience. In our example we use [Ginkgo](https://github.com/onsi/ginkgo) - BDD-style Go testing framework, 
   152  built on Go’s testing package, and allows to write readable tests in an efficient manner. It is best paired with
   153  the [Gomega](https://github.com/onsi/gomega) matcher library, but is designed to be matcher-agnostic.
   154  
   155  As with popular BDD frameworks in other languages, `Ginkgo` allows you to group tests in `Describe` and `Context` container blocks. 
   156  `Ginkgo` provides the `It` and `Specify` blocks which can hold your assertions. It also comes with handy structural utilities
   157  such as `BeforeSuite`, `AfterSuite`, etc that allows you to separate test configuration from test creation, and improve code reuse.
   158  
   159  `Ginkgo` also comes with support for writing asynchronous tests. This makes testing code that use channels with chaincode events 
   160  as easy as testing synchronous code.
   161  
   162  ### Test package 
   163  
   164  To write a new test suite, create a file whose name ends _test.go that contains the TestXxx functions, in our case will 
   165  be [cpaper_extended/chaincode_test.go](../examples/cpaper_extended/chaincode_test.go)
   166  
   167  Using separate package with tests [cpaper_extended_test](../examples/cpaper_extended/chaincode_test.go) instead of 
   168  [cpaper_extended](../examples/cpaper_extended/chaincode.go) allows us to respect
   169  the encapsulation of the chaincode package: your tests will need to import chaincode and access it from the outside. 
   170  You cannot fiddle around with the internals, instead you focus on the exposed chaincode interface.
   171  
   172  ### Import matchers and helpers
   173  
   174  To get started, we need to import the `matcher` functionality from the Ginkgo testing package 
   175  so we can use different comparison mechanisms like comparing response objects or status codes.
   176  
   177  We import the `ginkgo` and `gomega` packages with the `.` namespace, so that we can use functions from these packages 
   178  without the package prefix. This allows us to use `Describe` instead of `ginkgo.Describe`, and `Equal` instead 
   179  of `gomega.Equal`.
   180  
   181  
   182  ### Bootstrap
   183  
   184  The call to `RegisterFailHandler` registers a handler, the `Fail` function from the `Ginkgo` package. This creates the 
   185  coupling between `Ginkgo` and `Gomega`.
   186  
   187  Test suite bootstrap example:
   188  ```go
   189  package main
   190  
   191  import (
   192  	"fmt"
   193  	"testing"
   194  	"github.com/s7techlab/cckit/examples/insurance/app"
   195  	. "github.com/onsi/ginkgo"
   196  	. "github.com/onsi/gomega"
   197  	testcc "github.com/s7techlab/cckit/testing"
   198  	expectcc "github.com/s7techlab/cckit/testing/expect"
   199  )
   200  
   201  func TestCommercialPaper(t *testing.T) {
   202  	RegisterFailHandler(Fail)
   203  	RunSpecs(t, "Commercial paper suite")
   204  }
   205  
   206  var _ = Describe(`Commercial paper`, func() {
   207  	
   208  
   209  }
   210  ```
   211  
   212  ### Test structure
   213  This particular test specification can be written using `Ginkgo` as follows:
   214  
   215  ```go
   216  var _ = Describe(`CommercialPaper`, func() {
   217  	
   218              Describe("Commercial Paper lifecycle", func() {
   219              
   220                  It("Allow issuer to issue new commercial paper", func() { ... }
   221                  
   222                  It("Allow issuer to get commercial paper by composite primary key", func() { ... }
   223                  
   224                  It("Allow issuer to get commercial paper by unique key", func() { ... }
   225                  
   226                  It("Allow issuer to get a list of commercial papers", func() { ... }
   227                  
   228                  It("Allow buyer to buy commercial paper", func() { ... }
   229                  
   230                  It("Allow buyer to redeem commercial paper", func() { ... }
   231                  
   232                  It("Allow issuer to delete commercial paper", func() { ... }
   233              }
   234  }
   235  ```
   236  
   237  ### Implementing tests
   238      
   239  Now we go in depth to see how to create test functions specifically for chaincode development using `MockStub` features.
   240  
   241  #### Creating chaincode instance 
   242  
   243  Tests suite usually starts with creating a new instance of chaincode, or we can also instantiate a new chaincode 
   244  instance before every test spec. This depends on how and what we want to test. In this example we instantiate a global 
   245  `commercial paper` chaincode  that can  be used in multiple test specs.
   246  
   247  ```go
   248  	paperChaincode := testcc.NewMockStub(
   249  		// chaincode name
   250  		`commercial_paper`,
   251  		// chaincode implementation, supports Chaincode interface with Init and Invoke methods
   252  		cpaper_extended.NewCC(),
   253  	)
   254  ```
   255  
   256  ####  Test chaincode `Init` method
   257  
   258  All chaincode invocation (via SDK to blockchain peer or to MockStub) resulted as 
   259  [peer.Response](https://github.com/hyperledger/fabric/blob/release-1.4/protos/peer/proposal_response.pb.go) structure:
   260  
   261  ```go
   262  type Response struct {
   263  	// A status code that should follow the HTTP status codes.
   264  	Status int32 
   265  	// A message associated with the response code.
   266  	Message string 
   267  	// A payload that can be used to include metadata with this response.
   268  	Payload              []byte   
   269  }
   270  ```
   271  
   272  During tests we can check `Response` attribute:
   273  
   274  * Status (error or success)
   275  * Message string (contains error description)
   276  * Payload contents (marshaled JSON or Protobuf)
   277  
   278  `Testing` package [contains](expect/matcher.go) multiple helpers / wrappers on ginkgo `expect` functions.
   279  
   280  Most frequently used helpers are:
   281  
   282  * `ResponseOk` (*response* **peer.Response**) expects that peer response contains `ok` status code(`200`). Optionally you can pass either a `string` or an `OmegaMatcher` [any available matcher provided by Gomega](http://onsi.github.io/gomega/#provided-matchers), to check, for example, if the error contains a specific substring.
   283  * `ResponseError` (*response* **peer.Response**)  expects that peer response contains error status code (`500`). Optionally you can pass either a `string`, an `error` or an `OmegaMatcher` as for `ResponseOk`
   284  * `PayloadIs`(*response* **peer.Response**, *target* **interface{}) expects that peer response contains `ok` status code (`200`) 
   285  and converts response to **target** type using `CCKit` [convert](../convert) package
   286  
   287  For example we can simply test that `Init` method (invoked when the chaincode is initialised) returns successful status code: 
   288   
   289  ```go
   290  BeforeSuite(func() {
   291      // Init chaincode with admin identity
   292  
   293      adminIdentity, err := testcc.IdentityFromFile(MspName, `testdata/admin.pem`, ioutil.ReadFile)
   294      Expect(err).NotTo(HaveOccurred())
   295  
   296      expectcc.ResponseOk(
   297          paperChaincode.
   298              From(adminIdentity).
   299              Init())
   300  })
   301  ````
   302  
   303  ## Test the `Issue` method
   304  
   305  We expect that invocation of `issue` chaincode method will result in:
   306  * response with `Ok` status
   307  * event `IssueCommercialPaper` is fired
   308  
   309  In the test we can invoke `issue` method via MockStub, check response status and check chaincode event. Chaincode events 
   310  can be receive from `chaincodeEventsChannel`. The `BeEquivalentTo` method of the `expect` functionality 
   311  comes in handy to compare the event payload.
   312  
   313  ```go
   314  It("Allow issuer to issue new commercial paper", func(done Done) {
   315      //input payload for chaincode method
   316      issueTransactionData := &schema.IssueCommercialPaper{
   317          Issuer:       IssuerName,
   318          PaperNumber:  "0001",
   319          IssueDate:    ptypes.TimestampNow(),
   320          MaturityDate: testcc.MustProtoTimestamp(time.Now().AddDate(0, 2, 0)),
   321          FaceValue:    100000,
   322          ExternalId:   "EXT0001",
   323      }
   324  
   325      // we expect tha `issue` method invocation with particular input payload returns response with 200 code
   326      // &schema.IssueCommercialPaper wil automatically converts to bytes via proto.Marshall function
   327      expectcc.ResponseOk(
   328          paperChaincode.Invoke(`issue`, issueTransactionData))
   329  
   330      // Validate event has been emitted with the transaction data
   331      Expect(<-paperChaincode.ChaincodeEventsChannel).To(BeEquivalentTo(&peer.ChaincodeEvent{
   332          EventName: `IssueCommercialPaper`,
   333          Payload:   testcc.MustProtoMarshal(issueTransactionData),
   334      }))
   335  
   336      // Clear events channel after a test case that emits an event
   337      paperChaincode.ClearEvents()
   338      close(done)
   339  }, 0.1)
   340  ```
   341  
   342   This test will block until a response is received over the channel `paperChaincode.ChaincodeEventsChannel` (chaincode event).
   343   A deadlock or timeout is a common failure mode for tests like this. A common pattern in such situations is to add a select 
   344   statement at the bottom of the function and include a <-time.After(X) channel to specify a timeout. Ginkgo has this pattern
   345   built in. The body functions in all non-container blocks (`It` , `BeforeEache` etc ) can take an optional done `Done` argument.
   346   
   347   Done is a `chan interface{}`. When `Ginkgo` detects that the `done Done` argument has been requested it runs the body function 
   348   as a goroutine, wrapping it with the necessary logic to apply a timeout assertion. You must either close the done channel, 
   349   or send something (anything) to it to tell `Ginkgo` that your test has ended. If your test doesn’t end after a timeout period,
   350   `Ginkgo` will fail the test and move on the next one.
   351   
   352   The default timeout is 1 second. You can modify this timeout by passing a float64 (in seconds) after the body function.
   353   In this example we set the timeout to 0.1 second.
   354  
   355  ## Test the `Get` method
   356  
   357  We expect that invocation of `get` chaincode method will result in:
   358  * response with `Ok` status
   359  * response payload is marshaled `*schema.CommercialPaper` with certain attributes values
   360  
   361  `PayloadIs` allows to check response status and converts to `*schema.CommercialPaper`, then `Expect` helps to check
   362  equality of received data with expected values:
   363  
   364  ```go
   365  It("Allow issuer to get commercial paper by composite primary key", func() {
   366      queryResponse := paperChaincode.Query("get", &schema.CommercialPaperId{
   367          Issuer:      IssuerName,
   368          PaperNumber: "0001",
   369      })
   370  
   371      // we expect that returned []byte payload can be unmarshalled to *schema.CommercialPaper entity
   372      paper := expectcc.PayloadIs(queryResponse, &schema.CommercialPaper{}).(*schema.CommercialPaper)
   373  
   374      Expect(paper.Issuer).To(Equal(IssuerName))
   375      Expect(paper.Owner).To(Equal(IssuerName))
   376      Expect(paper.State).To(Equal(schema.CommercialPaper_ISSUED))
   377      Expect(paper.PaperNumber).To(Equal("0001"))
   378      Expect(paper.FaceValue).To(BeNumerically("==", 100000))
   379  })
   380  ```
   381  
   382  ### Test chaincode permissioning
   383  
   384  Each user can have different permissions to work with chaincode methods. All permissioning is based on user certificates 
   385  and Membership Service Provider Identifiers, which means that permissions always correspond to an X.509 certificate.
   386  
   387  The simple [car](../examples/cars) contains logic to control who can invoke `carRegister` method. 
   388  [Test](../examples/cars/cars_test.go) use `From` MockStub method to set certificate and MSP id of invoker
   389                                                                           
   390  ```go 
   391  
   392  	It("Disallow non authority to add information about car", func() {
   393  			//invoke chaincode method from non authority actor
   394  			expectcc.ResponseError(
   395  				cc.From(actors[`someone`]).Invoke(`carRegister`, cars.Payloads[0]),
   396  				ContainSubstring(owner.ErrOwnerOnly.Error())) // expect "only owner" error
   397  		})
   398  
   399  	It("Allow authority to add information about car", func() {
   400  			//invoke chaincode method from authority actor
   401  			expectcc.ResponseOk(cc.From(actors[`authority`]).Invoke(`carRegister`, cars.Payloads[0]))
   402      })
   403  
   404  ```
   405  
   406  
   407  
   408  ### Debugging operations with chaincode
   409  
   410  During test execution you may need to debug operations in chaincode method handler. `CCKit` supports
   411  Hyperledger Fabric 
   412  [Chaincode Logger](https://github.com/hyperledger/fabric/blob/release-1.4/core/chaincode/shim/chaincode.go#L1105)
   413  and its options, so you can use `CORE_CHAINCODE_LOGGING_LEVEL` environment 
   414  variable. `CCKit` [chaincode state wrapper](../state) outputs debug severity level messages, for example:
   415  
   416  ```shell
   417  ## CORE_CHAINCODE_LOGGING_LEVEL=debug go test
   418  Running Suite: Commercial Paper Suite
   419  =====================================
   420  Random Seed: 1559680577
   421  Will run 7 of 7 specs
   422  
   423  2019-06-04 ... [commercial_paper] Debug -> DEBU 001 router handler:  init
   424  2019-06-04 ... [commercial_paper] Debugf -> DEBU 002 state KEY: [OWNER]
   425  2019-06-04 ... [commercial_paper] Debugf -> DEBU 003 state check EXISTENCE OWNER
   426  2019-06-04 ... [commercial_paper] Debugf -> DEBU 007 state PUT with string key: OWNER
   427  2019-06-04 ... [commercial_paper] Debug -> DEBU 008 router handler:  issue
   428  2019-06-04 ... [commercial_paper] Debugf -> DEBU 009 state KEY: [_idx CommercialPaper ExternalId EXT0001]
   429  2019-06-04 ... [commercial_paper] Debugf -> DEBU 00a state check EXISTENCE _idxCommercialPaperExternalIdEXT0001
   430  2019-06-04 ... [commercial_paper] Debugf -> DEBU 00b state KEY: [_idx CommercialPaper ExternalId EXT0001]
   431  2019-06-04 ... [commercial_paper] Debugf -> DEBU 00c state PUT with string key: _idxCommercialPaperExternalIdEXT0001
   432  ```
   433  
   434  It shows there are several operations with chaincode state performed  while chaincode execution:
   435  * checked existence entry with `OWNER` key while handling `init` chaincode method
   436  * putted state entry with `OWNER` key
   437  * putted state entry with `[_idx CommercialPaper ExternalId EXT0001]` while handling `issue` chaincode method 
   438  (no uniq index for `Commercial entry` entity)
   439  ...
   440   
   441  ## Running test
   442  
   443  To run the test suite you have to simply run the command in the repository where the test suite is located:
   444  
   445  `go test`
   446  
   447  if you have any failures in test you can use `-ginkgo.failFast` option to disable running additional tests 
   448  after any test fails.
   449  
   450  
   451  ## Conclusion
   452  
   453  Chaincode `MockStub` is really useful as it allows a developer to test his chaincode without starting the network every time. 
   454  This reduces development time as he can use a test driven development (TDD) approach where he doesn’t 
   455  need to start the network (this takes +- 40-80 seconds depending on the specs of the computer).