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.
This tutorial is aimed to get started with Truffle and write some tests to check the basic features offered by Solidity.
- Create a truffle project and configure a development network
- Create and deploy smart contracts
- Interact with the smart contract from Truffle console
- Write tests for testing main features offered by Solidity
- Download and install node.js
- Install Truffle globally:
In order to install truffle globally on your machine, open a terminal and run this command
$ npm install truffle -g
- 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:
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.
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
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
1_initial_migration.js that will deploy the
Migrations.sol contract to the blockchain.
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 has created ten accounts with some amount of ether for executing the development tasks. It also opens the RPC
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
- Line 2 defines an object which is keyed by a network name that is
developmentin this case.
- On the next lines,
portparameters 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
- Line 1 specifies the version
pragmafor Solidity compiler.
- Line 3 defines the name of the
- Line 4 declares a public string variable
- Line 6 uses the
constructorfunction, 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
getterfunction that will return the value of name. By default, The compiler automatically creates
getterfunctions for all public state variables. However, for the sake of understanding, I have just created a
- Line 14 defines another function to change the value of our name variable.
This is a very simple smart contract with
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
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)
- 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)
truffle compile command.
In order to create a migration file, run the following command in the terminal:
$ truffle create migration deploy_sample_contract
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
requiremethod. 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
.solfile. 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
deployerobject as its first parameter. The
deployerobject 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
deployerin order to deploy our
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)
It will return the complete abstraction of our contract. We can get the
ABIof the contract by typing the following command:
Similarly, we can call the functions of our contract:
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');
When you are done playing with your smart contract, type
$ .exit to close the console mode.
4. Writing Tests
If we look at our contact again, we want to test:
- the constructor is successfully executed while creating the contract. This can be checked if our
getterfunction returns the same value as passed in the
changeNamemethod is successfully changing the value of the
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
“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.
Now let’s write the actual test for the
- In line 6, we have created the
instanceof 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.
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
- 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.
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) 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!
truffle-assertions package that we just installed, we can:
- check the event type
- check events parameters
- 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.