A BEGINNER FRIENDLY TUTORIAL

Test Driven Solidity with Truffle

Unit-Testing of Smart Contracts with Truffle, Ganache, Mocha, and Chai

Muhammad Yahya
OLI Systems
Published in
11 min readNov 22, 2018

--

Introduction

It is very common to write and compile Solidity code manually which is fine for small projects. However, as our project is growing bigger and bigger, it is good to have an automatic way of smart contracts development. In addition, testing Solidity code is crucial to avoid any problematic situation caused by a bug in the smart contract. There are different frameworks available for taking care of the development process. Truffle is one of them and is often regarded as the Ethereum Swiss Knife framework as it is a development environment, testing framework and asset pipeline for Ethereum.

Objectives

This tutorial is aimed to get started with Truffle and write some tests to check the basic features offered by Solidity.

  1. Create a truffle project and configure a development network
  2. Create and deploy smart contracts
  3. Interact with the smart contract from Truffle console
  4. Write tests for testing main features offered by Solidity

Prerequisites

  1. Download and install node.js
  2. Install Truffle globally:
    In order to install truffle globally on your machine, open a terminal and run this command
    $ npm install truffle -g
  3. Install ganache-cli:
    Ganache is a personal and local blockchain for Ethereum development and provides a very good environment to deploy smart contracts and run tests. Ganache is available as a desktop application as well as a command line tool. In this tutorial, we are going to install ganache as a command line tool by running the following command in the terminal:
    $ npm install ganache-cli -g

1. Creating a Truffle Project

In the next few lines of code, we are going to create a directory and initialize it with Truffle to create a basic project skeleton.

$ mkdir truffle-demo$ cd truffle-demo$ truffle init

On successful initialization, we will see the following message in the console:

If we look at the newly created directory, Truffle has created a boilerplate file structure:

Let’s have a brief walk through of these files and folders:

truffle.js

This is the configuration file that contains information such as development network settings for the project. This file will be used to configure the development network.

truffle-config.js

This is the same file as truffle.js and used in case of windows system run into a conflict problem while executing the generic Truffle commands. So, in that case, this file will be used instead of truffle.js file.

contracts

This folder is a container for all of our smart contracts. Truffle goes to the contracts folder and looks for the compatible files ( .sol ) to compile while compiling the project. The folder already contains a Migrations.sol file.

migrations

The configurations for migrating the contract over the network are written in a JavaScript file. These settings basically define how (for example: in what order) smart contracts will be deployed over the blockchain. All those migration files will go inside the migration folder. This folder contains a file 1_initial_migration.js that will deploy the Migrations.sol contract to the blockchain.

test

All the tests — either written in Solidity or JavaScript — will go to this folder.

In addition, Truffle will create a build folder to hold artifacts of the compiled contracts. We will see this folder when we run $ truffle compile command but before that, we need to set up our private network first.

2. Preparing the Test Environment

After having installed ganache-cli, open a terminal and run

$ ganache-cli

Ganache has created ten accounts with some amount of ether for executing the development tasks. It also opens the RPC port 8545at localhost.

Next, we need to configure network settings in truffle.js file as shown below:

In these lines of code, we have configured a single development network running on 127.0.0.1:8545.

  • Line 2 defines an object which is keyed by a network name that is development in this case.
  • On the next lines, hostand port parameters of the network are configured.
  • Line 6 gives a development network that matches any network it connects to.

So far we have initiated our truffle project, set up private network and ganache-cli running. In order to check if everything is set up correctly, type:

$ truffle test

We will see the following output:

Compiling .\contracts\Migrations.sol...0 passing (1ms)

Now we are good to go!

3. Smart Contract Development with Truffle

The general workflow for smart contract development with Truffle can be accomplished with the following steps:

Writing the Smart Contract

Let’s create a simple smart contract by typing the following command in the terminal:

$ truffle create contract SimpleContract

In the contracts folder, Truffle has just created SimpleContract.sol file with the smart contract boilerplate. Let’s populate this file with a bit of Solidity code:

We have just created a SimpleContractwhich contains a constructor, a getterand a settermethod.

  • Line 1 specifies the version pragma for Solidity compiler.
  • Line 3 defines the name of the contract.
  • Line 4 declares a public string variable name.
  • Line 6 uses the constructor function, that means this function will be automatically executed once when the smart contract is created. In this case, the value of the above declared variable name will be set to ‘my name’ when the contract is created.
  • Line 10 is the getter function that will return the value of name. By default, The compiler automatically creates getter functions for all public state variables. However, for the sake of understanding, I have just created a getter function here.
  • Line 14 defines another function to change the value of our name variable.

This is a very simple smart contract with constructor, one getter and one setter function. In the next step, we will compile this contract.

Compile the Smart Contract

In order to compile the smart contracts, run the following command:

$ truffle compile

This will compile all the smart contracts from the contracts folder and create artifacts in the contracts folder of the newly created build directory.

If we open the contract folder file from the build directory, we can see that these files are the abstraction of smart contracts and contain the following information:

  • contract name
  • ABI (a list of all functions along with their parameter and return values)
  • bytecode
  • deployed bytecode
  • compiler name and version
  • A list of networks onto which the contract has been deployed (once the contract is deployed)
  • address of the contract with respect to each network the contract is deployed to (once the contract is deployed)

These are .json files and act as a JavaScript wrapper to interact with the respective smart contracts.

Writing Migrations

In order to deploy smart contract over the blockchain, we first need to write the configurations for migration in a JavaScript file. These files are basically responsible for staging the deployment tasks of smart contracts over the blockchain. But, to deploy the smart contracts with migrations we need to access their artifacts which were created as a result of truffle compile command.

In order to create a migration file, run the following command in the terminal:

$ truffle create migration deploy_sample_contract

If you go to the migrations folder, you will see a JavaScript migration file is created which is prefixed by a random number and suffixed by a description. This is because the history of previously run migrations is recorded on the blockchain and truffle migrate will start execution from the last run migration. The numbered prefixed is required and is used to record either a migration has been run successfully or not. Open the newly created file in text editor and change the contents as follows:

  • In line 1, we are specifying which contract is going to use for interaction using artifacts.require()method. This method is similar to node.js require method. However, it will return a contract abstraction that will be used for the rest of the deployment script. The important thing here is we have to pass the name of the contract definition and not the name of .sol file. Because a source file may contain more than one contracts.
  • At line 3, module.export() is used to export the migrations. This method export a function which accepts a deployer object as its first parameter. The deployer object is the main interface for staging the deployment tasks and has many functions available to simplify migrations. At line 5, we are using deploy method from deployer in order to deploy our SimpleContract.

After writing the artifacts, make sure that ganache-cli is running in the background and run:

$ truffle migrate

Here we can see the address (SimpleContract: 0x4f5c2b6471915398132b627fad0f3089ea5ddd05) where our contract is deployed.

Interacting with the Smart Contract

As we now have the address of our deployed contract, we directly interact it using the truffle console. Run truffle console to open the console in the terminal:

$ truffle console

in the console type (replace the address with your address)

$ SimpleContract.at('0x4f5c2b6471915398132b627fad0f3089ea5ddd05');

It will return the complete abstraction of our contract. We can get the ABIof the contract by typing the following command:

$ SimpleContract.at('0x4f5c2b6471915398132b627fad0f3089ea5ddd05').abi;

Similarly, we can call the functions of our contract:

$ SimpleContract.at('0x4f5c2b6471915398132b627fad0f3089ea5ddd05').getName();

now, first run the changeName()function and then run getName() function to see if the name is changed to the new name or not.

$ SimpleContract.at('0x4f5c2b6471915398132b627fad0f3089ea5ddd05').changeName('your name');
$ SimpleContract.at('0x4f5c2b6471915398132b627fad0f3089ea5ddd05').getName();

When you are done playing with your smart contract, type $ .exit to close the console mode.

4. Writing Tests

In general, smart contract test can be written both in Solidity and JavaScript. In this tutorial, We will continue with JavaScript using async/await notation.

If we look at our contact again, we want to test:

  1. the constructor is successfully executed while creating the contract. This can be checked if our getterfunction returns the same value as passed in the constructor.
  2. the changeName method is successfully changing the value of the namevariable.

Under the hood, Truffle uses Mocha testing framework and Chai assertion library to test the smart contracts. Furthermore, truffle test command will run the tests from all the test files inside the testfolder.

But even before writing the tests in JavaScript, let’s write a test to test the constructor in plain English for our getter method:

“This method should return the name ‘my name’.”

You might have noticed that in order to test a method from a particular contract, we need to refer to that contract first. We will do that in our actual test by requiring the artifacts created earlier. Let’s create our test file by typing:

$ truffle create test SimpleContract

Open the newly created simple_contract.js file from the test folder and we will find a basic test sample there. We can see that Truffle is using contract() instead of describe() as its main function. The contract() provides a list of account available by the Ethereum client (in our case Ganache) which can be used to write tests. Furthermore, it also groups together all the tests for a specific contract. Let’s test the available accounts. Change the contents of your test file as below and run the following command:

$ truffle test
  • In line 1, we are using artifacts.require() method to request a usable contract abstraction for a specific Solidity contract.
  • In line 3, contract() method is used for grouping the tests. It uses a message in order to understand the purpose of the test.
  • In line 4, it() is a Mocha notation for a test case.

The actual test starts in line 4, what we are going to test. As a result, we will see a list of available accounts.

Testing Constructor

Now let’s write the actual test for the constructor:

  • In line 6, we have created the instance of our contract to access its methods.
  • In line 7, we are storing the return value of getName() method so that we can check it out.
  • In line 9, we are asserting that the value should be equal to ‘my name’.

Now run the $ truffle test command and we will see one test is passing!

Next, we will add another test for our changeName() method. Add the following test at line 11 below test 1.

In this test we are calling the changeName() method with ‘your name’ and in the next line, we are storing this value into a variable. Again run the $ truffle test command.

Testing Modifiers

The use of function modifiers in the Solidity is also a common pattern to have a better control over contract’s methods execution.

Let’s add a modifier onlyOwner() that will restrict the execution of a certain method only to the owner of the contract.

We have created a private variable owner and assign it to the creator of the contract using the constructor function.

  • In line 12, we have declared a function modifier which will check if the caller of the method is the owner of the contract, then continue ( _; ).
  • In line 22, the function modifier is assigned to the method changeName() by adding the name of the modifier.

Open the simple_contract.js file again and add the following test for the modifier. But before doing that, we will re-arrange the test to make our lives even easier.

Instead of creating the contract instance for every test, we have created a global instance of the contract using the beforeEach hook provided by Mocha.

As we have put the modifier for changeName() method, now only the owner should be able to change the name. This test is specified at line 23. If we run the $ truffle test command again, we see that the test is passed. This is because if we don’t specify the account, ganache automatically uses the first account. In this case, the owner of the contract and caller of the method are the same, so why the test is passed. In order to really check the modifier, let’s use the transaction object in our test:

Use any other account other than the first account ( accounts[0]) from the list of available accounts and run the test again. You will see your test is failed now because the only owner can call this method. Although, using any other account other than the owner will lead to failing the test but we want our test to pass. This is important, because although the test is asserting a failing behavior, a test itself should always pass its assertion. Here comes the idea of revert into play. The truffle-assertions library has the ability to assert that a transaction reverts as expected. The truffle-assertions package adds additional assertions that can be used to revert transactions in the smart contracts.

Install the package by running the following command in the terminal:

$ npm install truffle-assertions

Also import this package at the top or our test file, just below the liner requiring artifacts of the contract:

const truffleAssert = require('truffle-assertions');

add the following test and run the truffle test command again. At this time our test is passed with the expected behavior of the contract!

Listening Events

Using the truffle-assertions package that we just installed, we can:

  1. check the event type
  2. check events parameters
  3. print the event

Let’s add a name event that will be fired every time name is changed.

event NameEvent(string evPram);

now emit this event in the changeName() function as below:

add the following test to check if a specific event is present in the transaction or not and run the truffle test command:

We are saving the transaction into a result variable and then check that if ‘NameEvent’ is emitted by the transaction or not.

Now we will check if the event is emitted with correct parameters:

In the end, we are going to print out the event parameters and their values emitted by a particular event:

after running the truffle test command, you will see the transaction emitting the event, name of the emitted event, its parameter, and values.

The source code can be found here in this GitHub repo.

Related articles

Truffle
Truffle Assertions
Mocha
Chai

--

--