Annnnd I’m back. Gotta love when the cheap WordPress host you’ve used for years up and decides to shut down and you have to move in a hurry.

Ah well. It’s time to pick up where we left off, and while today’s subject may seem boring (because it is), it’s actually an INCREDIBLY important part of developing for any blockchain, Dragonchain included.

We’re gonna talk about testing our smart contracts and the challenges and tools we have to meet ’em. Let’s get after it.

Get the Git

If you haven’t checked out the Git since the last post, may want to take a look/pull again: I’ve added some code we’ll be looking at today (and FIXED some of the other code because of it!)

dragonchain-asset-tracker (this link opens in a new window) by Dragonchain-Community (this link opens in a new window)

A NodeJS smart contract, API server, web demo, and NFC-enabled Flutter mobile app demonstrating blockchain-enabled asset tracking.

Overview

In today’s post, I want to cover two main subjects:

  1. Manually testing and (if necessary) “resetting” data in the smart contract
  2. Code-based integration testing of smart contract methods

We’ll also touch on an upcoming post in which I’ll go into much greater detail on code testing.

Testing and Managing Data

Before I get into code-based testing that found a couple of bugs I missed and will hopefully help keep things bug-free in the future, I thought I’d talk about my approach to testing the contract, API, and web demo manually.

In fact, I went about it in exactly that order.

The first thing I tested going along was to make sure that my payloads were getting in, being processed, and spitting out the data I expected (at a glance at least). The simplest way to do this was with the DCTL tool.

dctl transaction create asset_tracker '{"method":"create_custodian", "parameters":{"custodian":{"type":"authority", "external_data": {"id": "1", "data":{"name":"Nike","description":"The Nike shoe brand"}}}}}'

And so on for each of the “methods” and parameter configurations I built.

Check this post out if you need a refresher on the basics of using the DCTL tool.

Next, once I had my smart contract methods mostly coded up and the API for interfacing to the contract started, I wanted a more rigorous way that I could test the multitude of methods and flows without having to manually create payloads over and over.

Enter Insomnia.

No, not the sleep disorder which all developers have on and off (or just on).

The Insomnia REST Client.

Using the Insomnia REST client

This amazing tool (and others like it) lets you build lists of API calls that you can then run in any order and over and over, plus so much more including trapping errors, dissecting the HTTP flow, setting up authentication, etc.

I don’t know how I’ve written web-based software without it before now.

Then, once I had my API humming along nicely, it was a fairly simple matter to code up a simple web interface that is entirely dumb and just calls the API for everything. Easy-peasy.

“Resetting” Data in the Contract

So I was manually testing, adjusting code, testing, adjusting code, etc., I obviously ended up with “bad” data in transactions created by my contract (incorrect outputs, missing data, etc.). I didn’t want those to show up in a “finished demo” or version of this system.

But this is a blockchain. The whole point is that data can’t go away or be changed.

So what do we do?

Use a nifty side-effect/feature of deleting and recreating smart contracts on Dragonchain. 😀

I mentioned it previously when I covered the basics of smart contracts, but it’s worth reiterating:

Once you’ve deleted a smart contract from your L1 (or a transaction type), you can no longer query for transactions that were created using that smart contract, even if you recreate the contract. The transactions exist and can be looked up manually (or by iterating over the BLOCKS for the chain), but they won’t show up in a search.

So when I pull a list of assets (by querying), it will only find transactions that were created since the most recent time I created the contract by that name.

While this means we need to be SURE that we’re happy with, for instance, our custom indexes before we go “live” with a contract, it also means that during development, we can effectively “reset” things by deleting the contract and recreating it.

If we don’t want to reset, we can simply rebuild our Docker container and use the DCTL contract update command to pull the latest version of the smart contract code without de-indexing all of our previous data. Nifty.

Now let’s move on to the testing I should have been doing from the very beginning (and will be from now on): integration testing.

Code-Based Integration Testing

Okay, so I think I’ve mentioned that I’m not a world-class developer.

I’ve never written unit tests (or integration tests) before this project.

Yep, I’m that developer.

In any event, given the critical nature of making sure our permanently-ledgered blockchain data is correct, now seemed like a great time to start learning unit/integration testing and TDD.

If you, like I was a few weeks ago, have no real idea what those words mean, go check out Eric Elliot on Twitter. Dude’s smart.

In my case, breaking things ALL the way down to unit tests was more than I wanted to try to deal with on a project that was already mostly finished, but integration testing was far more up my alley.

Again, for testing noobs like me: integration testing is useful for testing whole modules of code and their interactions with each other versus individual components or functions.

The First Problem: Impure Functions

When considering unit testing in particular, I was getting stuck trying to figure out how to FULLY isolate the methods in my smart contract into “pure functions” (for a given input to a function, you get a given output, it causes no side effects (like changing global state), and the function itself doesn’t do anything but transform the data (no internal looking up of other values, calling an API, etc.)).

In this case, most of my methods rely on ingesting parameter data that THEN might require looking up objects from the smart contract heap. Not “pure.”

BUT: it quickly became clear that with just a quick swap to using dependency injection to pass the Dragonchain SDK client object into my smart contract methods, I could treat my tests as integration tests to test input, output, and the interaction with the smart contract object heap.

Nifty!

The Next Problem: Using the Smart Contract Heap Without an Actual L1

Okay, so the point of unit/integration tests is that they should be run over and over and over and over (like after every code change) to make sure that a new bit of code in one place didn’t break something in another.

But if the point of our smart contract is to permanently ledger data (it is), how in the world can we test that without deleting and recreating the contract from a live L1 node for every test?

This is where that dependency injection is even more useful. Rather than passing in a real Dragonchain SDK client, I just passed in a dummy client that has the functionality I want and need for running my tests.

Here’s the whole of my mock Dragonchain SDK client:

tracker.client = {
    heap: {},

    updateSmartContractHeap: function (data) {
        this.heap = {...this.heap, ...data};

        // Write current heap to file //
        fs.writeFileSync(__dirname + '/post-run-heap.json', JSON.stringify(this.heap, null, 2), (err) => {    
            if (err) throw err;
        });
    },

    getSmartContractObject: async function (options) {

        if (this.heap[options.key])
        {
            return {
                "status": 200,
                "response": JSON.stringify(this.heap[options.key]),
                "ok": true
            }
        }

        return {
            "status": 404,
            "response": JSON.stringify({"error":{"type":"NOT_FOUND","details":"The requested resource(s) cannot be found."}}),
            "ok": false
          };          
    }
};

Since I only really need to manipulate the smart contract object heap inside my methods (rather than querying individual transactions), I only needed to replicate the getSmartContractObject function and the functionality for writing objects TO the heap.

Then I realized that I could add a simple file write whenever I updated the object heap to keep a copy of the heap on disk. It would be up-to-date with wherever my tests fail along the way (assuming they did), so I could take a look at any data that might be causing problems.

And where it REALLY becomes integration testing and not unit testing is that this lets me follow a whole flow of data through my smart contract methods.

For instance, I can do all of this in a series of automated integration tests without ever touching a live L1 or more complex data store:

  1. Create an authority custodian
  2. Create a non-authority custodian
  3. Create an asset group
  4. Create an asset
  5. Transfer the asset from authority to non-authority
  6. Add external data as the non-authority

Cool, huh?

You’ll see in the file at smart-contract/tests/index.js where I created the FULL list of simple asserts testing all of the contract methods and edge cases I could think of for both successes AND expected rejections (trying to add a second authority, trying to create an asset as a non-authority, etc.).

I seriously cannot recommend enough that you START every single code project with testing like this in place.

I found two separate bugs that were critical issues that I’d missed in my “manual testing.” Plus, anything I add to this code or change down the line will immediately let me know if I’ve broken something. Sweet.

Wrapping Up

I think that covers enough of what I wanted to hit on testing smart contracts without going down an even deeper rabbit hole.

I’ll dig into that whole in a future post. We have some AMAZING new tools from the Dragonchain team to talk about.

For now, I’m going to sign off, continue prepping for the upcoming Dragonchain hack-a-thon, and then start working on the NEXT post which will cover the API, web demo, and NFC-enabled app that wraps up this asset tracking project.

Until next time,
John

Leave a comment

Your email address will not be published. Required fields are marked *