Truffle is pretty good. If you have written a few smart contracts chances are you’ve used it or at least tried it out, but there are a few things which constantly pop up when writing test files for contracts which I find myself repeating. I’ve compiled a few of the most useful snippets that I use consistently when writing tests, maybe they will be useful to someone else too.

Deploying Contracts



* Deploys a contract with given constructor parameters; if the

* final param is an object it is treated as the intended

* transaction object

*

* @optional

* comma separated args to contract.new()

* @return newContract Deployed contract instance

*/

deploy: async (contract, ...args) => {

let txObj = _.last(args)

if (!_.isObject(txObj)) {

txObj = { from: web3.eth.accounts[0] }

args.push(txObj)

}

let newContract = await contract.new(...args)

return newContract

}, /*** Deploys a contract with given constructor parameters; if the* final param is an object it is treated as the intended* transaction object @param contract Contract to deploy (from artifacts.require()) @param ...args Additional arguments, passed as general* comma separated args to contract.new()* @return newContract Deployed contract instance*/deploy: async (contract, ...args) => {let txObj = _.last(args)if (!_.isObject(txObj)) {txObj = { from: web3.eth.accounts[0] }args.push(txObj)let newContract = await contract.new(...args)return newContract},

Often times I find myself needing to deploy a few different contracts within my tests, but the lines can be somewhat verbose. Using this little method, it’s really easy to deploy multiple different contract types using the same interface:

let MyContract = artifacts.require('./MyContract.sol')

utils.deploy(MyContract, param1, ..., { from: someSender, ... })

VM Exceptions (throw, revert(), require(), out of gas etc.)



* Assert that a particular Ethereum transaction throws

*

*

* failure

*

* completed (can be used with await)

*/

assertThrows: (promise, err) => {

return promise.then(() => {

assert.isNotOk(true, err)

}).catch((e) => {

assert.include(e.message, 'VM Exception')

})

}, /*** Assert that a particular Ethereum transaction throws @param promise Transaction operation which should throw @param err Error message which should be printed upon test* failure @return Promise which will resolve once the transaction has been* completed (can be used with await)*/assertThrows: (promise, err) => {return promise.then(() => {assert.isNotOk(true, err)}).catch((e) => {assert.include(e.message, 'VM Exception')})},

This isn’t the most bulletproof method for checking for a VM Exception, I’m sure. But it’s the most reliable one that I have come across so far. I think at one point TestRPC changed its error message string, throwing off a bunch of tests, so be warned.

Usage: await utils.assertThrows(myContract.someOperation(1)) . If your contract execution fails, the test will move on past this line, if it succeeds, you’ll get a test failure from the assert in the catch handler.

NOTE: This won’t work if you’re testing against a real network, only TestRPC. Bear that in mind.

Time-based Contract Testing



* Increases the time in the EVM. Cannot be undone at the moment

* which is a bit of a pain.

*

*/

increaseTime: async (seconds) => {

await web3.currentProvider.send({

jsonrpc: '2.0',

method: 'evm_increaseTime',

params: [seconds],

id: Number(Math.random() * 1000).toFixed(0)

})

await utils.mineOneBlock() // optional (*)

} /*** Increases the time in the EVM. Cannot be undone at the moment* which is a bit of a pain. @param seconds Number of seconds to increase the time by*/increaseTime: async (seconds) => {await web3.currentProvider.send({jsonrpc: '2.0',method: 'evm_increaseTime',params: [seconds],id: Number(Math.random() * 1000).toFixed(0)})await utils.mineOneBlock() // optional (*)

If you have any logic in a contract which relies on block.timestamp, you’ll probably want to test that without having to make a separate (non-production-version) contract to test out that little piece of logic. Luckily TestRPC allows you to increase the blocktime on it’s VM by using evm_increaseTime . Unfortunately you can’t roll this back yet, so you might either have to use relative dates/times in your tests or spin up some small helper which kills and restarts TestRPC after a test of this nature (so far I’ve never found that necessary though).

Also note the optional last line in this function, which might be necessary if your TestRPC’s blocktime is 0 (no auto-mining). If you check the blocktime immediately after calling this function, it won’t have changed, so you’ll need to skip ahead a block for the new time to be incorporated. If you’re wondering what mineOneBlock looks like, it’s down below.

Mining a Block

/**

* Sends a request to the RPC provider to mine a single block

* synchronously

*/

mineOneBlock: async () => {

await web3.currentProvider.send({

jsonrpc: '2.0',

method: 'evm_mine',

id: new Date().getTime()

})

},

Fairly self-explanatory, it tells TestRPC to mine a block for you :-)

Addendum: Waiting for Events



* Resolves when a given event happens, or rejects after an

* optional timeout

*

*

* for long blocktimes)

*

*/

waitForEvent: async (event, optTimeout) => {

return new Promise((resolve, reject) => {

let timeout = setTimeout(() => {

clearTimeout(timeout)

return reject(new Error('Timeout waiting for event'))

}, optTimeout || 5000)

event.watch((e, res) => {

clearTimeout(timeout)

if (e) return reject(e)

resolve(res)

})

})

} /*** Resolves when a given event happens, or rejects after an* optional timeout @param event Contract event to watch for @param optTimeout Optional time to wait in milliseconds (useful* for long blocktimes) @return Promise which resolves when the event is seen*/waitForEvent: async (event, optTimeout) => {return new Promise((resolve, reject) => {let timeout = setTimeout(() => {clearTimeout(timeout)return reject(new Error('Timeout waiting for event'))}, optTimeout || 5000)event.watch((e, res) => {clearTimeout(timeout)if (e) return reject(e)resolve(res)})})

This one is a little less clear than the others IMO, and again I don’t think it’s bulletproof (your network might respond too fast or something) but I think it’s good enough to start with. Essentially you’re going to trigger the event from your test asynchronously, then wait for the event (or lack thereof, it’s quite simple to make an inverted version of this function which ensures that no event was logged). Ensure that you don’t await on the function which is triggering the event, otherwise it will have already been logged by the time our helper starts watching for it, and we’ll always get a timeout.