Test Driven Development for Blockchain Apps with R3 Corda — How to Write Contracts and Unit Tests

May 31, 2018

Test Driven Development for Blockchain Apps with R3 Corda — How to Write Contracts and Unit Tests

Please note that I am not affiliated with R3.

This tutorial will explain and provide examples for how to write unit tests for Contracts in Corda using Java and IntelliJ. Testing for decentralized applications is especially critical because without the centralized authority it will be more complicated to roll out fixes or oversee corrections in the event of unintended consequences from bugs or security loopholes. This being said, it will be critical to ensure that all of your Contract code clauses are tested and working as expected before deploying a distributed app to production.

Note: We will be using Java for this tutorial, but the same principles apply for Kotlin if that is your preference. A basic familiarity with Java should make it simple to translate this code to Kotlin.


Getting Started

If you haven’t already, set up your development environment, and then set up a Corda project in IntelliJ by cloning the Java Template project from GitHub, which sets up the basic file structure, but has no real functionality.

Quick Overview of Concepts

If you are brand new to Corda, you should definitely review the Corda docs to get a basic conceptual understanding of the key concepts. However, we’ll quickly define the key concepts for this tutorial:

  • States the shared facts which Corda nodes reach consensus over and are then stored on the ledger
  • Contracts — set of rules that impose constraints on how states can be created on the ledger and how they can evolve
  • Flows — encapsulate the procedure for carrying out a specific ledger update
  • Commands — provided with a proposed ledger update to indicate intent of the transaction; affects what validation rules are applied.

File Structure

For our purposes, we only really care about 3 directories for now:

  1. cordapp/src/main/… (Flows will go here — and other things.)
  2. cordapp/src/test/… (Unit tests will go here. ? ?)
  3. cordapp-contracts-states/… (Contracts and States will go here.)

Technical Note: The file structure of the Template has two separate packages (cordapp and cordapp-contract-states). This separates the Contract and State objects from the Flows and the rest of the application classes — the reason being that the Contract and State objects need to be passed across the network, so by putting them in a separate package we can avoid wasting bandwidth.


The template project has a placeholder State, Contract, and Flow — let’s start with these and make a few changes to get us started:

TemplateState.java

cordapp-contract-states/src/main/java/[package]

Here is the TemplateState as is from the repo:

TemplateContract.java (As is from GitHub Java Template repo)

We’re going to create a trivial example State for storing ledger updates about the balance of an individual’s bank account. Let’s replace the contents of this class with the following:

Tip: You can auto generate getter methods in IntelliJ by right clicking an instance declaration and clicking Generate -> Getters and selecting all of your variables.

TemplateContract.java

cordapp-contract-states/src/main/java/[package]

Here is the TemplateContract as is from the repo:

TemplateContract.java (As is from GitHub Java Template repo)

Let’s add the following into the verify() method:

Here we:

  1. Extract the command from the Transaction. If there is none then an exception is automatically thrown by the requireSingleCommand() method.
  2. Check and see if the command is of one of our defined Command types. In this case, we only have one type: Action. If it is not of this type then throw an exception.

What about the unit tests!?

Now we’re ready to start implementing TDD and developing out our Contract specifications.

The Template project already has an IntelliJ Run Configuration set up for running Contract, Flow, and Integration tests. Right now we have a dummy contract test defined in ContractTests.java (cordapp/src/test/..), so let’s go ahead and try it out:

And we should see:

Preparing for a Unit Test

Corda has a built-in MockServices interface that makes it very easy to write Contract tests. Using the “ledger” method, our mock services will provide a clean slate each test with no pre-existing transaction or services. This way we can be sure each test is isolated with no side effects.

We’ll also utilize a TestIdentity, which will be a mocked node— or participating party in the blockchain. When instantiating the TestIdentity, we’ll use a CordaX500Name, specifying the name of the individual or organization, as well as the country and locality. These values can be whatever you want. The identity might represent a bank, a company, an individual, or any other entity that would be a participant in your blockchain app.

ContractTests.java

cordapp/src/test/java/[package]

How to Use the MockServices

Let’s discuss the use of the ledger() method. See below:

  1. We call ledger, providing our instantiated MockServices object which we’ve named ledgerServices.
  2. Then we have a predicate expression ledger -> {} . Everything inside of these brackets will be in our isolated blank-slate mock ledger.
  3. Within our ledger expression, we will create a new transaction using the method ledger.transaction(tx -> {});. Everything inside of these second set of brackets will be corresponding to the new transaction, referenced by variable tx.
  4. For now, we don’t care about the return values, so we’ll return null from both expressions.

Add a Unit Test

Now let’s get rid of that dummyTest and replace it with something legitimate.

Let’s say that for now the ledger only supports adding a bank account and balance entry, but not altering or removing it. In this case, our transactions will produce one output state — a TemplateState entry that contains an account number and balance — and consumes NO input states. So our contract will need to make sure that a transaction does not include an input state if it is to be approved as valid.

Let’s add our first unit test, which will verify that an exception is thrown if a transaction is issued which contains an input state.

Let’s run the unit test!

And…

1 test failed. ? ? ?

Uh oh..

Just kidding, that’s what we want, because we haven’t written any code in our Contract class to throw the exception the test is checking for!

java.lang.AssertionError: Expected exception but didn’t get one?

This is the TDD philosophy. Test first, code second. We should be able to make this test pass without making any changes to the test itself.

Note: If you get an error saying “Constructor parameter — ‘arg0’ — doesn’t refer to a property of …”, no big deal! You just need to add the following lines to your .idea/compiler.xml file:

<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" />
</component>

If you still have issues, try rebuilding, gradle clean, restarting IntelliJ, etc. to make sure nothing is cached.

Write Contract Code

Let’s add some code into our contract to prevent ledger updates from being approved if the transactions has any inputs states.

Using the requireThat() method, we can have any number of require.using() clauses — each of which takes a string to be used for the exception message, and an expression that will throw the exception if evaluated to true. Here, we grab the list of inputs from our transaction by calling getInputs() on our injected transaction variable tx, and then check to see if it’s empty.

Now when we re-run our unit test..

It passes! ? ? ?

Continue the cycle => write failing test, make it pass

Let’s add a few more Contract unit tests for good measure:

Run the tests..

?

Looks scary, but you’ll get used to it. ?

The new tests fail. Add the contract clauses to make them pass:

Note that on Line 8 we grab the first (any only) output state from our transaction object tx and cast it to a TemplateState so that we can check its properties.

And re-running tests…

All passing now!

? ? ?


Okay.. So tests are passing but how do I know things are really safe? I mean, what if I added a contract code rule and forgot to add the unit test. Do I need unit tests for my unit tests!?

No, that would be ridiculous. But I see your point. How do you know your tests are really covering all the bases? Using a Code Coverage tool, packaged with IntelliJ, we can find out.


? Excuse me, Professor Brenden, I have a question.

? What is it, Sandra?

? It seems like you’re just teaching us how to use IntelliJ, which I already know. Who cares? I just want to make a decentralized bank account ledger for my own personal use.

? True. And if you already know about code coverage, sorry for boring you. The point I want to stress is that for blockchain applications it is critical that all of the verification rules are tested properly, because without a centralized authority it is much more difficult to issue fixes and corrections!

? Boring, I’m going to skip ahead!

? ? OK.. Well skip down to the section on Writing More Advanced Contract Tests.


Code Coverage!

IntelliJ has code coverage built in and its super easy. The only catch is we’ll have to reformat our contract code a bit to make coverage analysis possible for our verification rules. As elegant looking as the requireThat method is, we’re going to convert to good ol’ fashioned if statements:

This code is identical to what we had before, but will allow us to see if the “throw new IllegalArgumentException” lines get evaluated or not. If they do, we know that we have a unit test that causes that exception to be thrown — if not, then we know that code is never reached and that we are lacking a test.

So let’s test it out. First let’s comment out one of our unit tests, so we can let Code Coverage alert us to its absence.

Now, make sure that code coverage is enabled by going to “Edit Run Configurations” and selecting “Run Contract Tests” and clicking the “Code Coverage” tab. Make sure one of the options is selected; either will work.

Click the third icon to run the tests with Code Coverage

Code Coverage results

Look on the far left near the line numbers. You’ll see that next to each “throw new..” line we see a green rectangle — except for Line 39, where we see a red one. Red indicates the line was not reached, and therefore we know that the exception for “1 required signer” was never thrown — meaning there is no test for it!

If we un-comment that test, then voila! ?

Line 39 now passes Code Coverage.

Now, you can be sure all of your contract rules have associated unit tests! If your unit tests are all passing and your code coverage shows all your Exceptions being thrown, then you can feel confident in your code.


Writing More Advanced Contract Tests

Here is an overview of how you can construct more complicated tests.

MockServices

For some functionality, you may want to provide parameters to the MockServices. This can include providing a list of cordapp packages to scan and injecting a TestIdentity and IdentityService into the MockServices object for example. By default it is just going to use a dummy TestIdentity when instantiated with no parameters.

Test Methods

Methods for building our unit test transactions:

  • tx.inputs() — Add inputs
  • tx.outputs() — Add outputs
  • tx.attachments() — Add attachments by SecureHash
  • tx.command() — Add commands
  • tx.timeWindow() — Add TimeWindows
  • tx.tweak() — Create a isolated copy of transaction
  • tx.verifies() — Contract verifies assertion
  • tx.fails() — Contract verification raises exception
  • tx.failsWith() — Contract verification raises exception with given message

Methods for ledger using MockServices:

  • l.transaction() — Verified transaction
  • l.unverifiedTransaction() — Unverified transaction
  • l.attachment() — Upload Attachment
  • l.tweak() — Create an isolated copy of ledger

tweak() ?

Okay, not Meth, but something a lot safer! This can be used on either the ledger level or transaction level to create a local isolated copy (of either the ledger or transaction). You can modify and assert on the copy without affecting the original. This is used so that you can have multiple fails() assertions within a single test, testing different error conditions.

Cheat Sheet

Remarks

Thanks for reading — I hope this was helpful!

If you enjoyed the article and want more, comment below. Possible future topics could include Flows and Flow unit testing, Integration testing, and developing a front-end application for use with your CorDapp.

If you’re interested in hiring a developer for blockchain or full-stack web development, contact me via my website.

Cheers! ? ?

References

https://docs.https://corda.net/key-concepts.html

https://docs.https://corda.net/tutorial-test-dsl.html

https://github.com/corda/obligation-cordapp

https://stackoverflow.com/questions/48772259/java-io-notserializableexception-constructor-parameter-arg0-doesnt-refer-to-a

Resources

Corda Documentation — Corda Sample Projects — Corda Slack Group

Share: