Testing Solidity with Truffle and Async/Await
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.
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.
npm install -g truffle
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:
- Node ≥ 7.6 confirm by entering
node -v
in command line - npm ≥ 4 confirm by entering
npm -v
in command line - Truffle ≥ 3.3 confirm by entering
truffle -v
in command line - 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.
- git clone https://github.com/sogoiii/truffle-async-await-tests
cd truffle-async-await-tests
npm install
oryarn install
truffle compile
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:
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:
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.
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.
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.
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.
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.
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!
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
- Copy Trading | Crypto Tax Software
- Grid Trading | Crypto Hardware Wallet
- Crypto Telegram Signals | Crypto Trading Bot
- Best Crypto Exchange | Best Crypto Exchange in India
- Bitget Review | Gemini vs BlockFi | OKEx Futures Trading
- Best Crypto Trading Bots in the US | Changelly Review
- Earn Passive Income Using Crypto Arbitrage In India
- Huobi Review | OKEx Margin Trading | Futures Trading
- Best Crypto APIs for Developers
- Best Crypto Lending Platform
- An ultimate guide to Leveraged Token