Chapter 11. Improving the development cycle with Truffle

This chapter covers

  • Installing Truffle, a smart-contract framework
  • Setting up and compiling an Ethereum contract within Truffle
  • Simplifying contract deployment through Truffle’s migrations
  • Simplifying contract unit testing with Truffle

In the previous chapter, you started to integrate unit testing into your development environment with Mocha. But you might have noticed that the unit test script didn’t look ideal. It has a fairly complex setup, which includes explicit instructions on compiling SimpleCoin and creating a contract factory. Each test also includes an explicit deployment statement that references the contract factory. The main drawback is that all this infrastructural code distracts from the main objective of unit tests, which is to focus on functional aspects of a contract. Another disadvantage is that if you wanted to create a new test suite to cover a different contract, you’d have to duplicate all this infrastructural code.

Wouldn’t it be nice if you had a way to simplify the deployment of a contract? This is the main objective of Truffle, an Ethereum contract development framework that focuses on streamlining deployment and consequently simplifying unit testing.

In this chapter, you’ll set up Truffle, and then you’ll use it to improve the compile -> deploy -> test cycle. The main focus will be on learning how to use the tool, so, to avoid getting distracted by contract-specific issues, you’ll reuse our good old SimpleCoin contract yet again. If you’re eager to implement something new, please bear with me: in the next chapter you’ll develop a brand new Dapp from scratch using Truffle!

11.1. Setting up Truffle

You can install Truffle easily with Node.js npm, and then you can start to create a project. Install version 4.1.15, which is the one I have used, as follows:

C:>npm install -g [email protected]
Warning

In order to run my code smoothly, don’t install Truffle 5.0.0. My code is written against version 4.1.15, and is unlikely to work correctly under version 5.0.0.

11.2. Moving SimpleCoin under Truffle

You’ll start with a minimal project, and then you’ll integrate SimpleCoin in Truffle by walking through the entire development lifecycle, including the following steps:

  • Setting up SimpleCoin in Truffle
  • Compiling the contract
  • Deploying the contract
  • Unit testing the contract

11.2.1. Setting up SimpleCoin in Truffle

Create a working directory for the SimpleCoin Truffle project. I’ve created mine as follows:

C:EthereumTruffleSimpleCoin

Open an OS shell and move to this directory:

C:>cd EthereumTruffleSimpleCoin

Now you can initialize the Truffle project:

C:EthereumTruffleSimpleCoin>truffle init

Truffle will create the following directory structure and prepopulate it with some files:

/contracts
    Migrations.sol
/migrations
    1_initial_migration.js
/test
truffle.js
truffle-config.js

Table 11.1 provides a description of each directory.

Table 11.1. Truffle directory structure

Directory/filename

Description

/contracts Directory for Solidity contract files you want to compile and deploy.
/contracts/Migrations.sol Special contract that Truffle uses to deploy project contracts.
/migrations JavaScript configuration files to perform migrations (more on this later).
/test Truffle can automatically test both Solidity contract code and JavaScript application code through unit tests placed in this folder.
truffle.js The Truffle project configuration file for Linux or macOS.
truffle-config.js The Truffle project configuration file for Windows.
Warning

If you’re running Windows, keep truffle-config.js and remove truffle.js. If you’re running on Linux or macOS, you can remove truffle-config.js and use truffle.js. In the rest of the chapter, I’ll refer to truffle.js—you’ll understand why shortly.

11.2.2. Compiling SimpleCoin

Copy the latest version of SimpleCoin from C:EthereummochaSimpleCoinSimpleCoin.sol to C:EthereumTruffleSimpleCoinContractsSimpleCoin.sol. Downgrade the pragma solidity instruction of SimpleCoin.sol to 0.4.23 after copying the file, as the latest version of Truffle at the time of writing uses solc 0.4.23.

You perform contract deployment in Truffle through so-called migrations, as I’ll explain shortly. You execute these through a Migrations contract, shown in the following listing, which was auto-generated in the contracts folder when you initialized the project with the truffle init command.

Listing 11.1. contracts/Migrations.sol
pragma solidity ^0.4.23;

contract Migrations {
  address public owner;
  uint public last_completed_migration;

  constructor() public {
    owner = msg.sender;
  }

  modifier restricted() {
    if (msg.sender == owner) _;
  }

  function setCompleted(uint completed) public restricted {
    last_completed_migration = completed;
  }

  function upgrade(address new_address) public restricted {
    Migrations upgraded = Migrations(new_address);
    upgraded.setCompleted(last_completed_migration);
  }
}

You can now kick the compilation

C:EthereumTruffleSimpleCoin>truffle compile

and you’ll see output similar to this:

Compiling .contractsSimpleCoin.sol...
Writing artifacts to .uildcontracts

After a successful compilation, Truffle has created a new directory relative to the project folder, /build/contracts. The folder contains compilation artifacts, described in table 11.2, that you’ll use during the deployment stage.

Table 11.2. Compilation artifacts

Artifact

Purpose

Migrations.json ABI interface and bytecode of Migrations.sol
SimpleCoin.json ABI interface and bytecode of SimpleCoin.sol

11.2.3. Troubles with Truffle

If you’ve started having issues at this point, it might be because you’re working in Windows or have a compiler versioning problem. You’ll need a solution so you can complete the work in this chapter. This two-part section provides some advice to help you solve such problems.

Truffle on Windows

If you’re working in Windows, you might experience issues when running any Truffle command. Specifically, if you’re running Truffle compile in a Windows command shell, you might get the cryptic Microsoft JScript runtime error shown in figure 11.1.

Figure 11.1. Error generated when running a Truffle command in Windows

The error occurs because Windows can’t distinguish correctly between the Truffle.cmd command file in the npm folder (typically in C:UsersYOURNAMEAppData Roaming pm) and the truffle.js file in your Truffle project folder. You have four options for solving this issue:

  • Use a configuration file named truffle-config.js instead of truffle.js, as mentioned in the earlier setup section. Remove truffle.js from your Truffle project folder.
  • If for any reason you want to use a configuration file named truffle.js rather than truffle-config.js, use Git Bash rather than the standard command shell.
  • Call truffle.cmd explicitly, for example, truffle.cmd compile.
  • Go to the directory where truffle.cmd is and copy it locally with another name, for example, truff.cmd. Then run truff compile instead of Truffle compile. If you decide to use this workaround, keep typing truff rather than Truffle for the rest of the chapter.
Troubleshooting Truffle Compile Errors

If you get compilation errors, it might be because you’re running an old version of the compiler or because Truffle is referencing an old version of the compiler, even if you’ve upgraded it recently. Specifically, after executing the truffle compile command, you might get some compilation error messages due to new constructor syntax introduced in solc 0.4.22. You have two options for fixing the issue. The best approach is to uninstall Truffle and reinstall it globally by entering

C:EthereumTruffleSimpleCoin>npm uninstall truffle -g

followed by

C:EthereumTruffleSimpleCoin>npm install [email protected] -g

Now if you re-execute truffle compile, you should get at most some warnings associated, possibly, with the Migrations contract (in the contractsMigrations.sol file). It might still be implemented under an old constructor convention, depending on the version of Truffle you’re running. If you’re running Truffle 4.1.15 or later, you shouldn’t have any issues; the Migrations.sol file should have been autogenerated like in listing 11.1. If you’re running an older version and want to get rid of the warnings, replace

constructor() public {
  owner = msg.sender;
}

with

function Migrations() public {
  owner = msg.sender;
}

If you recompile, you should no longer get any warnings:

C:Ethereum	ruffleSimpleCoin>truffle compile
Compiling .contractsMigrations.sol...
Writing artifacts to .uildcontracts

The second way to fix compilation issues, which I’d leave as a last resort, is to reinstall solc, but only into the project folder:

C:EthereumTruffleSimpleCoin>npm install [email protected]

11.2.4. Deploying SimpleCoin onto a mock network client through migrations

A migration is a deployment script. As seen before, migration scripts are in the migrations directory. Replace the content of 2_deploy_contracts.js with the script shown in the following listing.

Listing 11.2. 2_deploy_contracts.js: SimpleCoin's migration script
var SimpleCoin = artifacts.require("SimpleCoin")       1

module.exports = function(deployer) {                  2
    deployer.deploy(SimpleCoin, 10000);                3

};

  • 1 artifacts.require is similar to Node.js require, and you have to initialize it with the name of a contract (not a contract filename).
  • 2 You have to set the module.exports property to a function that accepts a deployer object, which is the component performing deployment tasks.
  • 2 SimpleCoin gets deployed with an initial supply of 10,000 tokens.

During the first migration execution, you also have to deploy the Migrations contract. This contract gets deployed through its own migration script, shown in the following listing and contained in this file: CEthereumTruffleSimpleCoinmigrations1_initial_migration.js.

Listing 11.3. 1_initial_migration.js: Migrations’ migration script
var Migrations = artifacts.require(“Migrations”)   

module.exports = function(deployer) { 
    deployer.deploy(Migrations);
};

Once you’ve placed the migration scripts in the migrations directory, open a separate OS shell and move to the project folder:

C:>cd EthereumTruffleSimpleCoin

You’ll be deploying SimpleCoin onto Ganache, the mock Ethereum client you already saw in chapters 8 and 10. Ganache is part of the Truffle framework. If you haven’t already installed it, you can do so through npm, as usual:

C:Ethereum	ruffleSimpleCoin>npm install -g [email protected]

Then, after the installation is complete, start it up:

C:>cd EthereumTruffleSimpleCoin>ganache-cli

Make sure truffle.js (or truffle-config.js on Windows) is configured to point to Truffle Develop, as shown in the following listing, as opposed to pointing to a public test network. Amend its content as shown in the following listing.

Listing 11.4. truffle.js (or truffle-config.js): Truffle pointing to Test Develop
module.exports = {
  networks: {
    development: {
      host: "localhost",
      port: 8545,
      network_id: "*" // Match any network id
    }
  }
};
Deploying from a specific account

By default, your contracts will be deployed under Truffle from accounts[0], which is the default transaction sender. If you want the deployment transaction to be submitted from another account, you must specify the full address in the truffle.js file in the from property, as shown here:

module.exports = {
  networks: {
    development: {
      host: "localhost",
      port: 8545,
      from: "0xf17f52151ebef6c7334fad080c5704d77216b732",   1
      network_id: "*" // Match any network id
    }
  }
};

  • 1 This is Ganache’s accounts[1], as shown on Ganache’s startup screen.

Go back to the previous console and run

CEthereumTruffleSimpleCoin>truffle migrate

Then you’ll get output like this:

Using network 'development'.
Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0x5823254426b34ec4220be899669e562d4691a72fa68fa1956a8eb87f9f431982
  Migrations: 0x8cdaf0cd259887258bc13a92c0a6da92698644c0
Saving successful migration to network...
  ... 0xd7bc86d31bee32fa3988f1c1eabce403a1b5d570340a3a9cdba53a472ee8c956
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying SimpleCoin...
  ... 0x21c4120f9f231ea5563c2a988de55440139ba087651d3d292f06ae65434580f7
  SimpleCoin: 0x345ca3e014aaf5dca488057592ee47305d9b3e10
Saving successful migration to network...
  ... 0xf36163615f41ef7ed8f4a8f192149a0bf633fe1a2398ce001bf44c43dc7bdda0
Saving artifacts...

This shows that SimpleCoin has been deployed successfully on Ganache’s mock network. If you prefer to run the mock network client and Truffle commands from within the same OS shell, you can use a separate console called Truffle Develop. Have a look at the sidebar if you want to learn more. For the rest of the book, you’ll keep using two separate consoles: one to run Ganache and the other to launch Truffle commands.

Executing Truffle commands from Truffle Develop’s console

You can start Truffle Develop from the Truffle project folder, as follows:

CEthereumTruffleSimpleCoin>truffle develop

This will start a mock network client similar to Ganache (and TestRPC), but on port 9545, which means, if you have Ganache running, you don’t need to shut it down.

Truffle Develop startup screen

But if you want to use Truffle Develop, you need to change the truffle.js configuration to point to port 9545:

module.exports = {
  networks: {
    development: {
      host: "localhost",
      port: 9545,
      network_id: "*" // Match any network id
    }
  }
};

Now you’ll be able to run Truffle commands, such as migrate, directly from the Truffle Develop console:

Truffle(develop)>migrate

You’ll see exactly the same output as before.

11.2.5. Deploying SimpleCoin onto a public test or production network

Although in this chapter you’ll be deploying contracts only on Ganache (or TestRPC), at some point you might want to deploy on the public test network or production network. If so, you must modify your truffle.js (or truffle-config.js) file and include configurations for a test network and a live network, as follows:

module.exports = {
    networks: {
       development: {
       host: "localhost",
       port: 8545,
       network_id: "*"
    },
    live: {
        host: "localhost", 
        port: 80,
        network_id: 1,        
    },
    ropsten: {
        host: "localhost", 
        port: 80,
        network_id: 3,        
    }  
}};

This configuration assumes you’re running a local geth node (the host is pointing to localhost), and the default Web 3 provider will be instantiated by Truffle as

new Web3.providers.HttpProvider("http://<host>:<port>")

But if you decide to use a different provider, for instance an HDWalletProvider pointing to a Ropsten Infura network, you must configure it explicitly with the provider property:

networks: {
  ...
  ropsten: {
    provider: new HDWalletProvider(mnemonic, "https://ropsten.infura.io/"),
    network_id: '3',
  },
  ...

Also, bear in mind, when deploying on a public network, you might want to specify other settings, such as the gas limit, the gas price, and the account the contract should be deployed through. If so, you can add relevant configuration properties as described in table 11.3, but I invite you to consult the official documentation before proceeding further: http://mng.bz/mmgy.

Table 11.3. The truffle.js configuration properties
Property Purpose
gas: Gas limit (default = 4712388)
gasPrice: Gas price (default = 100,000,000,000 wei)
from: Sender address of the deployment transaction (and contract owner)
provider: The default is web3, as explained previously.

Once you’ve configured truffle.js (or truffle-config.js) appropriately, you can kick a deployment to the Ropsten network as follows:

CEthereumTruffleSimpleCoin>truffle migrate --network ropsten

And, as you might have guessed, you’d deploy to MAINNET as follows:

CEthereumTruffleSimpleCoin>truffle migrate --network live

11.2.6. Testing SimpleCoin

Truffle supports clean-room testing, which means that if a contract has been deployed on Ganache, its state will get reinitialized at the beginning of each test file being processed. If the contract has been deployed on a public network, the migrations will get re-executed at the start of each test file being processed, effectively redeploying the contract from scratch before the execution of the tests. In general, during development it’s preferable to run unit tests against Test Develop, as they run up to 90% faster. It’s advisable to run the tests on a private network, and ultimately on a public network, in later stages of the development cycle to make sure you’ve tested application aspects of communicating with a real network.

It’s possible to write two different classes of tests with Truffle:

  • Solidity tests—Test the contract logic from test contracts
  • JavaScript tests—Test the contract from external Web3.js calls, which go through the same infrastructure as real calls

You must place all test scripts, whether written in Solidity or JavaScript, in the test directory of the Truffle project. We’ll focus almost entirely on JavaScript tests, as Solidity tests are meant to test contract-to-contract interaction, which is a more advanced topic outside the scope of this book. But I’ll give you some of the basics of working with Solidity tests.

11.2.7. Writing Solidity tests

You implement Solidity tests through custom test contracts whose code you have to place in .sol files within the project test directory. Solidity tests must be able to run against

  • the assertion library that Truffle provides
  • any other assertion library
  • Ganache (or TestRPC)
  • any Ethereum client (not only geth) and type of network (both private and public)

You structure a Solidity test contract the way TestSimpleCoin.sol does, as shown in listing 11.5. You must follow these guidelines and conventions:

  • You must import an assertion library to check for equality, inequality, and emptiness. The default assertion library that Truffle provides is Truffle/Assert.sol.
  • You must import the Truffle/DeployedAddresses.sol library so the test runner can access the addresses of contracts deployed through migrations. The Deployed-Addresses library is recompiled each time the test contract is run, to guarantee clean-room testing.
  • You must import the contract under test.
  • The name of a test contract must start with Test (uppercase T) so the test runner can identify it easily.
  • The name of a test function must start with test (lowercase t).
  • A test function must return a Boolean value. Generally, this is returned through an assertion function such as Assert.equal().
Listing 11.5. TestSimpleCoin.sol test contract
pragma solidity ^0.4.2;

import "Truffle/Assert.sol";                                  1
import "Truffle/DeployedAddresses.sol";                       2
import "../contracts/SimpleCoin.sol";                         3


contract TestSimpleCoin {                                     4

  function testInitialBalanceUsingDeployedContract() public {
    SimpleCoin simpleCoin = SimpleCoin(
      DeployedAddresses.SimpleCoin());                        5

    uint expected = 10000;

    Assert.equal(simpleCoin.coinBalance(tx.origin), expected, 
      "Owner should have 10000 SimpleCoin initially");        6
  }
}

  • 1 Imports assert library
  • 2 Imports DeployedAddresses contract
  • 3 Imports contract being tested: SimpleCoin
  • 4 Testing contract
  • 5 SimpleCoin instance at the deployed address
  • 6 Verifies test assumptions

First, place TestSimpleCoin.sol in the test folder and make sure Test Develop (or TestRPC) is still running. (If it’s not running, restart it in a separate console.) You can then run Solidity tests by executing this command:

CEthereumTruffleSimpleCoin>truffle test 

You’ll get output similar to this:

Using network 'development'.

Compiling .contractsSimpleCoin.sol...
Compiling .	estTestSimpleCoin.sol...
Compiling Truffle/Assert.sol...
Compiling Truffle/DeployedAddresses.sol...

  TestSimpleCoin
    √ testInitialBalanceUsingDeployedContract (48ms)

  1 passing (530ms)
Warning

Depending on the version of your solc compiler, you might get some warnings, especially around code raising events.

As I mentioned earlier, contract-to-contract testing is an advanced topic outside of the scope of this book, so I won’t cover Solidity tests further. The next section will be dedicated entirely to JavaScript tests under Truffle.

11.2.8. Writing JavaScript tests

Do you remember the effort you put into writing tests in Mocha? It wasn’t wasted: Truffle supports Mocha JavaScript testing, but with deeper integration with Ethereum. This means you

  • won’t have to decorate your test files with require statements for Ethereum libraries such as web3.js
  • will be able to reference contracts that have been deployed through migrations, without having to compile them and deploy them manually
  • will be able to reference accounts implicitly, without having to hardcode Ethereum addresses
  • will be able to run the tests from within Truffle and integrate them within any continuous integration jobs that Truffle coordinates

To run in Truffle the JavaScript tests you wrote earlier in Mocha, you’ll have to make minor changes, especially if you want to ensure they’re executed in clean-room mode:

  • Replace describe() calls with contract() calls, which ensures all contracts get redeployed onto the Ethereum client and tests are run with a clean contract state.
  • Reference solidity contracts with artifacts.require(), as you did earlier when writing the migration scripts.
  • Replace the callback section within the asynchronous deployment call you had on Mocha tests with a promise chain, where the first promise in the chain is a promise of a deployed contract, and the subsequent promises are chained through then() statements.

I’ll demonstrate here how to rewrite some of the tests, so you can get a better idea of what the guidelines I’ve given mean. I’ll start from the first test against SimpleCoin’s constructor. For convenience, I’ve repeated the code from the original test you wrote in Mocha in the following listing, so you don’t have to flip pages back and forth.

Listing 11.6. Original Mocha test on the constructor, verifying contract ownership
var assert = require('assert');

const source = fs.readFileSync('c:/Ethereum/mocha/SimpleCoin/SimpleCoin.sol', 'utf8');
const compiledContract = solc.compile(source, 1);
const abi = compiledContract.contracts[':SimpleCoin'].interface;
const bytecode = '0x' + compiledContract.contracts[':SimpleCoin'].bytecode;
const gasEstimate = web3.eth.estimateGas({ data: bytecode }) + 100000;

const SimpleCoinContractFactory = web3.eth.contract(JSON.parse(abi));

describe('SimpleCoin', function() {
  this.timeout(5000);
  describe('SimpleCoin constructor', function() { 
    it('Contract owner is sender', function(done) {

        //arrange 
        let sender = web3.eth.accounts[1]; 
        let initialSupply = 10000; 

        //act
        let simpleCoinInstance = SimpleCoinContractFactory.new(initialSupply, { 
            from: sender, data: bytecode, gas: gasEstimate}, 
            function (e, contract){ 
            if (typeof contract.address !== 'undefined') {
                //assert
                assert.equal(contract.owner(), sender);
                done();
            }
            });
    });
 });
});

Now create a file called testSimpleCoin.js and place it in the test directory. Fill it with the code shown in the following listing.

Listing 11.7. Truffle constructor test verifying contract ownership
const SimpleCoin = artifacts.require(
   "./SimpleCoin.sol");                                 1

contract('SimpleCoin', function(accounts) {             2
  contract('SimpleCoin.Constructor', 
    function(accounts) {                                3
    it("Contract owner is sender", 
       function() {                                     4
       return SimpleCoin.deployed()                     5
       .then(function(instance) {                       6
           return instance.owner();                     7
        })
       .then(function(contractOwner) {                  8
          assert.equal(contractOwner.valueOf(), 
          accounts[0],                                  9
          "accounts[0] wasn't the contract owner");     10
       });
    });
  });
});

  • 1 References SimpleCoin, the code under test
  • 2 Name of the test suite
  • 3 Name of the test section
  • 4 Description of the single test
  • 5 Gets a promise of a SimpleCoin contract instance deployed through the migration you set up earlier
  • 6 Chains the promise of a SimpleCoin instance to a new function
  • 7 Gets a promise of the owner of the SimpleCoin instance
  • 8 Chains the promise of the owner of the SimpleCoin instance to a new function
  • 9 The final function executed in the promise chain performs the test assertion.
  • 10 Error message to display in case of test failure
Note

As I mentioned in the deployment section, by default, contracts are deployed under Truffle from accounts[0]. That’s why in the test you’re comparing the address of the contract owner, contractOwner.valueOf(), with accounts[0].

You’ve surely noticed a few differences between Mocha and Truffle tests, such as those listed in table 11.4.

Table 11.4. Differences between Mocha and Truffle tests

Mocha tests

Truffle tests

Mocha’s test script starts with a relatively long setup, during which we go through all the necessary low-level steps required for deploying SimpleCoin. Truffle’s test has hardly any setup at all and immediately references a deployed instance of SimpleCoin (through the migration framework).
Mocha’s test execution is based on a callback associated with the deployment of a SimpleCoin instance. Truffle’s test execution is based on a chain of promises starting with a promise of a deployed SimpleCoin instance.
Once you get ahold of a SimpleCoin instance (or a promise of it), Mocha’s test code seems shorter and to the point. After getting a SimpleCoin instance, Truffle’s code goes through various steps to get ahold of the contract owner for subsequently comparing it with the expected one.

In summary, thanks to Truffle’s migration framework, tests don’t need much setup to access an instance of SimpleCoin. On the other hand, after referencing a SimpleCoin instance, Mocha’s test seems less verbose than Truffle’s.

Let’s run the test! When you re-execute the Truffle test command, both Solidity and JavaScript tests will be run, and you’ll get output similar to this:

C:EthereumTruffleSimpleCoin>Truffle test
Using network 'development'.

Compiling .contractsSimpleCoin.sol...
Compiling .	estTestSimpleCoin.sol...
Compiling Truffle/Assert.sol...
Compiling Truffle/DeployedAddresses.sol...

  TestSimpleCoin
    √ testInitialBalanceUsingDeployedContract (49ms)

  Contract: SimpleCoin
    Contract: SimpleCoin.Constructor
      √ Contract owner is sender

  2 passing (1s)

You can rewrite the constructor test that verifies the owner balance is the initial supply. Add the following it() block in testSimpleCoin.js:

it("Contract owner balance is equal to initialSupply", function() {
    return SimpleCoin.deployed()
    .then(function(instance) {
        return instance.coinBalance(accounts[0]);
    }).then(function(contractOwnerBalance) {
        assert.equal(contractOwnerBalance.valueOf(), 
          10000, 
          "the contract owner balance is not equal to the full supply of 
 10000");
    });
});  
Note

As you might remember, SimpleCoin’s migration script (named 2_deploy_contracts.js and shown in listing 11.2) sets the initial supply to 10,000. That’s why the contract owner balance is being compared to 10,000.

Rerun the tests:

C:EthereumTruffleSimpleCoin>Truffle test
  TestSimpleCoin

    √ testInitialBalanceUsingDeployedContract (49ms)


  Contract: SimpleCoin

    Contract: SimpleCoin.Constructor

      √ Contract owner is sender

      √ Contract owner balance is equal to initialSupply


  3 passing (1s)
Improving JavaScript tests with await/async

In the file named testSimpleCoin_ALL_sync.js provided on the book website, you can find a full Truffle test suite equivalent to the test suite you wrote earlier in Mocha. I encourage you to go through the tests in detail and compare the related tests.

You’ll come to the conclusion that the ideal structure for a test is a sort of chimera combining the easy setup of Truffle tests and the simple and direct code within Mocha’s callbacks. There is indeed a way to achieve this chimera, and it’s through JavaScript’s async/await syntax.

Note

JavaScript’s async/await syntax allows you to perform asynchronous processing through syntax that resembles that of synchronous programming—much simpler than that of typical asynchronous programming techniques such as callbacks or promise chains. If you’re interested in learning more about JavaScript asynchronous coding, I recommend Secrets of the Java-Script Ninja by John Resig, et al, published by Manning.

Warning

To take advantage of async/await, you must be running on Node.js version 8.0 or higher. I also advise you to install Truffle version 4.0 or higher.

Here’s how the first constructor test, which verifies contract ownership, looks using async/await:

const SimpleCoin = artifacts.require("./SimpleCoin.sol");

contract('SimpleCoin', function(accounts) {
  contract('SimpleCoin.Constructor', function(accounts) {
    it("Contract owner is sender", async function() {
                    
      let simpleCoinInstance = 
        await SimpleCoin.deployed();                     1
      let contractOwner = 
        await simpleCoinInstance.owner();                2
                    
      assert.equal(contractOwner.valueOf(), 
         accounts[0], 
         "accounts[0] wasn't the contract owner");       3
        });
    });
});

  • 1 Gets the deployed instance of SimpleCoin
  • 2 Gets the contract owner
  • 3 Verifies the contract owner is what you expect

As you can see, by replacing the promise chain of the initial test version with statements based on async/await, the code looks as simple as a plain synchronous implementation would look. You’ve achieved exactly what you were looking for:

  • Minimal (zero) SimpleCoin contract setup
  • Simple test implementation

Place this test in a new file—for example, called testSimpleCoin_asyncawait.js. Put it in the test folder and run it as usual (removing testSimpleCoin.js and TestSimpleCoin.sol from the test folder before doing so, so they don’t get executed):

C:EthereumTruffleSimpleCoin>truffle test

You will get this output:

Using network 'development'.

  Contract: SimpleCoin
    Contract: SimpleCoin.Constructor
      √ Contract owner is sender

  1 passing (53ms)

I challenge you to convert the second constructor test, which verifies the owner balance is the initial supply, from the earlier version based on a promise chain to async/await statements. Look away and write your implementation before comparing your solution with mine!

Have you finished? Does your test look similar to this?

it("Contract owner balance is equal to initialSupply", async function() {
    let simpleCoinInstance = await SimpleCoin.deployed();
    let contractOwnerBalance = 
        await simpleCoinInstance.coinBalance(accounts[0]);

    assert.equal(contractOwnerBalance.valueOf(), 
        10000, 
        "the contract owner balance is not equal to the full supply of 
 10000");
});

If you’re still unconvinced of the benefits of moving from tests based on promise chains to tests based on async/await, I’ll show you a more dramatic comparison. Here’s the chain-promise-based version of a test on a successful transfer() operation:

contract('SimpleCoin.transfer', function(accounts) {
  it("Succesful transfer: final sender and recipient balances are correct", function() {
    //arrange 
    let sender = web3.eth.accounts[0];
    let recipient = web3.eth.accounts[1];
    let tokensToTransfer = 200;
                    
    const expectedSenderBalance = 9800;
    const expectedRecipientBalance = 200;
                    
    //act
    return SimpleCoin.deployed()                            1
    then(function(instance) {
      simpleCoin = instance;
        return simpleCoin.transfer(recipient, 
               tokensToTransfer, {from: sender});           2
    }).then(function() {
         return simpleCoin.coinBalance(sender);             3
    }).then(function(balance) {
         sender_ending_balance = balance.toNumber();        4
         return simpleCoin.coinBalance(recipient);          5
    }).then(function(balance) {
         recipient_ending_balance = balance.toNumber();     6

      //assert
      assert.equal(sender_ending_balance, 
          expectedSenderBalance, 
             "Amount wasn't correctly taken from the sender");
      assert.equal(recipient_ending_balance, 
          expectedRecipientBalance, 
             "Amount wasn't correctly sent to the receiver");
    });                                           
  });               
});

  • 1 Gets a promise of a deployed SimpleCoin instance
  • 2 Gets a promise of the SimpleCoin instance after performing the transfer operation you’re testing
  • 3 Gets a promise of the sender balance
  • 4 Assigns the sender balance to a variable
  • 5 Gets a promise of the recipient balance
  • 6 Assigns the recipient balance to a variable

As you can see, having to reference balances through promises before assigning them to variables makes the test rather convoluted. Here’s the equivalent async/await version of this test:

contract('SimpleCoin.transfer', function(accounts) {
  it("Succesful transfer: final sender and recipient balances are correct", 
 async function() {
    //arrange 
    let sender = web3.eth.accounts[0];
    let recipient = web3.eth.accounts[1];
    let tokensToTransfer = 200;
                    
    const expectedSenderBalance = 9800;
    const expectedRecipientBalance = 200;
                    
    let simpleCoinInstance = await SimpleCoin.deployed();
                    
    //act
    await simpleCoinInstance.transfer(recipient, 
        tokensToTransfer, {from: sender});
    let sender_ending_balance = 
        await simpleCoinInstance.coinBalance(sender);    
     let recipient_ending_balance = 
        await simpleCoinInstance.coinBalance(recipient);                

    //assert
    assert.equal(sender_ending_balance.valueOf(), 
        expectedSenderBalance, 
        "Amount wasn't correctly taken from the sender");
    assert.equal(recipient_ending_balance.valueOf(), 
        expectedRecipientBalance, 
        "Amount wasn't correctly sent to the receiver");

  });               
});

Isn’t this clearer? I’m so confident you can understand this version of the test without any explanation that I decided I didn’t need to annotate the code at all. If you have Node.js 8 (or higher) installed, you should definitely consider writing your tests with async/await rather than promise chains. I encourage you to try and convert all the chain-promise-based Truffle tests you find in the testSimpleCoin_ALL_sync.js file to equivalent async/await ones.

Summary

  • When writing a Mocha test, you must typically provide fairly complicated initialization code at the top of your script to deploy your contract through various steps, including solc compilation. Your test is placed in the callback associated with the contract deployment call.
  • Truffle is a contract development environment that simplifies contract compilation, deployment, and testing.
  • Truffle performs contract deployment through migrations, based on simple configuration.
  • When writing a Truffle test, you don’t need to provide any initialization code for contract compilation and deployment, but you must write tests with asynchronous code based on a promise chain, which might not be as readable as equivalent Mocha tests.
  • A way to make Truffle tests much more readable is to write them using async/ await. But to do so, you must upgrade to Node.js 8 or higher.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset