Testing Smart Contracts with Truffle

Mike Calvanese
Level K
Published in
4 min readJul 25, 2017

If you’ve started building DApps on Ethereum, you’re probably using the Truffle Framework. Truffle provides many features to make your life as a smart contract developer easier. In this post, I’ll focus on truffle test and describe my experience writing tests for a very basic smart contract.

Getting Started

Before reading this, you should have some familiarity with developing Ethereum smart contracts and Solidity. If not, I recommend reading:

I reference my example project a few times in this post. This project contains some helpful patterns that you can use to test your contracts. I would also recommend looking at the tests in zeppelin-solidity. These are cleanly written and contain some useful reusable patterns.

You’ll also want to be familiar with testrpc, since this is the environment you’ll most often run tests on.

Truffle Testing Basics

Tests in a Truffle project can be written in Javascript or Solidity. While creating the example project, I was able to implement all of my test cases in Javascript. I’d recommend starting with Javascript tests if you’re coming from a JS background. So far, I’ve been able to cover all of my test cases with JS.

Running truffle test executes all tests in your truffle project. This does the following:

  1. Compiles your contracts
  2. Runs migrations to deploy the contracts to the network
  3. Runs tests against the contracts deployed on the network

Keep in mind that you can run truffle test against testrpc, a testnet, or even the main net (although that would be quite expensive). The recommended approach is to run against testrpc during development, and run against a testnet before deploying to main net. The primary advantages of running against testrpc are speed and the ability to run tests on a clean environment.

Avoiding Interactions

I ran into some problems with unwanted interactions between my tests. These were happening for me for a few reasons:

First, the examples in the Truffle docs use MyContract.deployed() to test against the contract instance deployed by truffle test. This presents a problem - if the state of MyContract.deployed() is modified by a test, this will affect subsequent tests. I ended up running MyContract.new() before every test instead, so each test is executed with a clean contract state. By doing this, I was able to execute single tests or change the order of tests without having to worry about getting different results.

Another problem I had was related to testrpc account balances. When testrpc starts up, it is seeded with 10 accounts. Each one has a balance of 100 ETH. I had a few tests that involved these accounts paying ETH to my contract. With testrpc running in the background, and after many runs of my test suite, these accounts were depleted and my tests started failing. One obvious way to solve this is to write tests with smaller transactions. I opted instead to add an npm script to run testrpc, run my test suite, and kill testrpc after the test suite completes:

(testrpc > /dev/null & truffle test) && kill -9 $(lsof -ti :8545)

OpenZeppelin uses a similar technique, implemented in this script

Wrangling Your Promise Chains

Every call or transaction you execute from web3.js or truffle is asynchronous. Truffle uses promises. web3.js uses callbacks. Either way, you’re going to be writing a ton of asynchronous code.

I can’t stress enough how much async/await will help. You’ll have to upgrade to Node 7.6 or higher. It may be the best decision of your life. Here’s an example, before using async/await:

Using async/await, this becomes:

With async/await you’re going to have a much easier time abstracting test helpers (see these examples). Your code will be less error prone, more readable, and more concise.

Debugging

At the time when I’m writing this, there’s no debugger for Ethereum smart contracts. This might feel like a bit of a hack, but I found it useful to write a helper function to output the state of my contract. The output logger for the ThresholdPool contract looks like this:

(here it is in the example project)

Within your tests, you can execute the state logger before or after making modifications to contract state. Since we’re only reading data here and not modifying contract state, this should not affect the test results or account balances.

Mock Contract Pattern

Truffle and testrpc don’t provide any mechanism for mocking globally available variable values. You can accomplish this by implementing a mock contract. In the following example, I use a mock contract to mock current time, provided by the now variable in Solidity. The same pattern can be applied in order to mock any value in your contracts.

First, I added a currentTime() function to my ThresholdPool contract and have it return now:

currentTime() is used throughout ThresholdPool.sol to obtain the value of now. I override this in my mock contract, ThresholdPoolMock.sol, so that it returns a different value:

Since ThresholdPoolMock extends ThresholdPool, I can now run all of my tests against ThresholdPoolMock. The mock contract lets me set the return value of currentTime() to any value when deploying my contract. I added another function, changeTime(), which lets me change the return value of currentTime() after the contract is deployed. Now I can run a transaction, call changeTime() to simulate passage of time, then run another transaction and test the result. Here’s an example.

*Note that you can also update time on testrpc using evm_increaseTime, see this example.

That’s It!

Hopefully this helps you get started with your tests. Building a test suite while developing new smart contracts is an absolute necessity. Using truffle console or a similar client to test manually is useful for debugging, but not a replacement for test coverage. Since there’s real money at stake, security and stability of your solidity code is incredibly important. I encourage you to leverage well tested contracts from well established libraries whenever possible. But when you choose to build contracts from scratch, make sure the code you deploy is well tested!

--

--

Mike Calvanese
Level K

Reading and writing bytes32's. Spending too much on gas. Co-Founder and Lead Developer @ Brink | brink.trade