Testing Solidity with Truffle and Async/Await

Angello Pozo
Coinmonks
Published in
5 min readAug 10, 2017

--

If you were to ask a programmer/developer what the worst part of the job is, I think most would say writing tests. You have to create mock data and do coding backflips to get specific things to work properly. On top of that you have to write many different cases for a single feature/function. It takes a lot of time write good tests. But with the introduction of async/await in node 8, writing tests becomes much nicer! Its what we used to create over 100 tests for HelloSugoi.

Discover and review best Blockchain softwares

Audience:

Mostly for developers who are writing Ethereum applications in Solidity and are writing tests for their code in Javascript. And for anyone curious about making async/await and how it can help write better tests.

Requirements/installation:

With node 8, async/await was enabled by default. Prior to version 7.6 you could use async/await as long as you added the --harmony-async-await flag. For version prior you could also use Babel to transpile down to ES5 compatible javascript. I suggest installing Node 8 or above for this project to work properly. You can use homebrew or nvm to manage the version you are using.

In addition to having node installed you need to have Truffle and Ganache-cli installed globally.

  1. npm install -g truffle
  2. npm install -g ganache-cli

Truffle is used to compile and migrate Solidity contracts onto Ethereum networks. Ganache-cli is used as a fake Ethereum node that returns immediately instead of 15 seconds. To confirm you have everything installed try the following:

  1. Node ≥ 7.6 confirm by entering node -v in command line
  2. npm ≥ 4 confirm by entering npm -v in command line
  3. Truffle ≥ 3.3 confirm by entering truffle -v in command line
  4. Ganache-cli ≥ 5.0.0 confirm by entering ganache-cli -v in command line

Example Project Setup:

I have an example project repo with all our code. If you want to work on it, please clone and install its dependencies.

  1. git clone https://github.com/sogoiii/truffle-async-await-tests
  2. cd truffle-async-await-tests
  3. npm install or yarn install
  4. truffle compile
  5. truffle test

The fourth command runs all the tests and they should successfully complete.

How Async/Await Works:

Conceptually, async/await makes asynchronous JavaScript code look like synchronous code. Let’s compare the two:

Comparing simple truffle test as promise chain or async/await

From the sample above, I think the status2 method looks cleaner than the status method. It literally has 4 lines of boilerplate, which is more then the 3 lines of logic in status2. I think the choice is easy!

To use async/await you need to add the async moniker to the function declaration. This tells the v8 interpreter to look for the await key, Otherwise node throws an error asking what await is. You put await in front of any promise function/method, not a callback! If your function is a callback, I suggest using a package like bluebird or the native promisify method that was introduced in Node 8. Luckily, Truffle returns promises instead of callbacks.

An async function (async function(){}) also returns a promise! This is nice because then you can chain promises or async functions if desired. Like in the example below:

Async returns a promise

A while ago async/await had a decently large performance hit. It was big enough that not using async/await was reasonable. Since the release of v8 5.5 performance has only gotten better. Unless you are doing something computationally heavy, using async/await may be a good alternative.

Example (Easy Case):

Truffle is nice because .deployed returns a promise. Remember, only place await in front of promises!

Open the test/metacoin.js file and look at the very first test.

Truffle easy test example

The first test only does 3 operations. The first is to get the deployed contract JavaScript object and sets it to the variable meta. Then it calls the function getBalance.call() function behind the await keyword. Again, this is because .call returns a promise. Finally, we simply check that the function returned the expected value inside an assert. Easy peasy! All its checking is if the balance of meta is 1000 as per the test title.

When you run truffle test test/metacoin.js in the command line, you will see how all the tests run successfully.

Example (Error Case):

To catch errors with async/await, you need to wrap the call inside of a try/catch.

Truffle fail test example

In the test above, I am calling a function that I know does not exist in the Solidity contract. Await will throw an error that is caught in the try/catch. Because I want to verify that calling someNonExistentFn throws an error, I return true inside of the catch to exit out of the test. If await meta.someNonExistentFn() was successful, the catch block would not be called and the next line throw new Error(...)would be hit. Thus telling me that he function failed to throw.

Example (Time Travel):

Sometimes a Solidity contract may have a function that only works when called in the future. In MetaCoin.sol I add the onlyAfterDate function modifier to specialFn where I define when in the future a function may be callable.

MetaCoin.sol with time info only

Inside of the MetaCoin constructor I manually set endTime to 1 days. Time variables in Solidity are uint of epoch seconds. The onlyAfterDate function modifier uses endTime , which means any function with onlyAfterDate will only be callable in 1 days.

To test this functionality, we are not going to wait 1 whole day! And what happens when the time is more dynamic, rather than hard-coded? Ganache-cli added a RPC method called evm_increaseTime that will time travel the blockchain.

Ganache-cli evm_increaseTime as a promise

All we have to do is make an RPC call to web3 with evn_increaseTime and params. I wrapped the callback-based web3 into a promise so we can use it with async/await.

With that new function, we simply await with a time and boom! It’s a few days later! If you are on Testrpc 4.0.0 (and older version of ganache-cli), there is a bug where a block must be mined for evm_timeTravel to take effect. You can review the bug here.

Successful time travel with Truffle and Ganache-cli

Large Comparison:

The two snippets below test for the exact same thing. The only difference is one is written as a promise chain and the other with async/await. Just driving the point home!

Large Promise Chain Truffle Test
Large Async/Await Truffle Test

Conclusion:

I hope you can see how much nicer tests can be with async/await. A lot of boilerplate goes away, which increases readability. Catching errors may be more tedious, but they are manageable. Plus if you need to time travel for you tests, Ganache-cli provides a method to update time. If you are using promises already, then integrating with async/await is very easy. If you have a lot of callbacks, then using require('utils').promisify makes your life easy. See the trend?

Join Coinmonks Telegram Channel and Youtube Channel get daily Crypto News

Also, Read

--

--

Angello Pozo
Coinmonks

Co-Founder of HelloSugoi. Hacking away on Ethereum (blockchain) DApps. Follow me on https://twitter.com/angellopozo