QA InfoTech » Blockchain » The Ultimate Guide To Test Your Solidity Smart Contracts
The Ultimate Guide To Test Your Solidity Smart Contracts
23 Oct, 2019

The Ultimate Guide To Test Your Solidity Smart Contracts

When we hear about blockchain technology, we mainly think of either the cryptocurrencies or the smart contracts. Initially, it was about the cryptocurrencies when Bitcoin gained the market attention and the crypto world came to boom. But, then blockchain technology is not all about just cryptocurrencies, it is more about immutable distributed ledger for digital assets (which can be defined through smart contracts). The major use cases of the blockchain are KYC (Know Your Customer), SCM (Supply Chain Management), Medical Record Keeping, Energy Trading, Crowdfunding, etc.

Smart Contract Developers can develop the contract and perform manual testing. This is for beginner level only; it does not make sense to develop an application and deploy it on production without performing any testing. And it is quite mandatory for the situation as once you deploy the contract, you can’t modify it.

This can be achieved only by deploying a modified contract which will result in a new contract address; if you refer to the new address then you will lose all the data stored in the previous contract. One way to do this is to use the Upgradable Smart Contract approach, but it’s not really a good idea to upgrade the contract for every single bug. It’s better to perform testing operations for your smart contract thoroughly so a contract once deployed need not be modified.

In this blog post, we will be talking about the smart contract testing using truffle framework. Smart contract test cases can be written using solidity and Javascript/typescript. We will focus on Javascript test cases using async/await. One can write the test cases using .then function as well.

The blog is written with an assumption that you have a basic understanding of smart contract development using solidity and the truffle framework. If not, you can first go through the solidity documentation and our truffle framework blog or truffle documentation for reference. For this tutorial, we will be using the simple Coin contract provided as an example in the solidity documentation.

Preparations:

Truffle Setup:  I am assuming you are done with the truffle setup using the truffle init command. If not, you can follow the truffle documentation.

Prepare Contract File: Create a contract file in the /contracts directory naming Coin.sol. The contract code is given below:


pragma solidity >=0.5.0 <0.7.0; contract Coin {    // The keyword "public" makes variables    // accessible from other contracts    address public minter;    mapping (address => uint) public balances;




   // Events allow clients to react to specific

   // contract changes you declare

   event Sent(address from, address to, uint amount);




   // Constructor code is only run when the contract

   // is created

   constructor() public {

       minter = msg.sender;

   }




   // Sends an amount of newly created coins to an address

   // Can only be called by the contract creator

   function mint(address receiver, uint amount) public {

       require(msg.sender == minter, "Unauthorized Access");

       require(amount < 1e60, "Amount limit exceeded");

       balances[receiver] += amount;

   }




   // Sends an amount of existing coins

   // from any caller to an address

   function send(address receiver, uint amount) public {

       require(amount <= balances[msg.sender], "Insufficient balance");

       balances[msg.sender] -= amount;

       balances[receiver] += amount;

       emit Sent(msg.sender, receiver, amount);

   }

}

In the contract defined above, one can use modifiers as well. For now, we are just keeping it simple. There are two functions mint and send defined in the contract. The Mint function can be called by the minter or contract creator only to transfer coins to the receiver address and the Send function can be called by any address who has sufficient balance to transfer coins to any other address.

Let’s write the test cases using javascript

Truffle framework is built on top of mocha and uses chai for assertions. If you are not familiar with writing tests in mocha, you can follow the mocha and chai documentation.

Let’s first create a file under /test directory naming coinTest.js. (Note: Keep in mind while creating a test file that it should end with the Test keyword, otherwise your test will not be executed for that file)

Contract abstraction can be used for making contract interaction possible from javascript; we can do so by requiring the contract artefacts (make sure you require the artefacts using the contract name, not the file name) by using the following code:

const Coin = artifacts.require("Coin");

In truffle, we will use contract() instead of describe(). Accounts can be accessed through the accounts parameter (defined in the code below). Let’s define some variables which we will be using in the test cases. Also, define the beforeEach hook for deploying the new smart contract before executing each test case.

contract("Coin", accounts => {
   let owner = accounts[0];
   let instance;
   let amount = 1000;
   let transferAmount = 500; 
   beforeEach(async function () {
       instance = await Coin.new({ from: owner });
   });
})

Now, we will start writing the test cases for constructor, mint and send function. Test cases include scenarios such as, to test minter’s account, to test minter’s balance, to test mint function for minter’s account or with some other account, to test send function for sufficient and insufficient balance. The complete test file is given below:

 const Coin = artifacts.require("Coin");
 
contract("Coin", accounts => {
 
   let owner = accounts[0];
   let instance;
   let amount = 1000;
   let transferAmount = 500;
 
   beforeEach(async function () {
       instance = await Coin.new({ from: owner });
   });
 
   it("should check owner account as minter", async () => {
       let minter = await instance.minter();
       assert.equal(
           minter,
           owner,
           "Owner Account is not the minter"
       );
   });
 
   it("should check minter's balance", async () => {
       let balance = await instance.balances(owner);
       assert.equal(
           balance.valueOf(),
           0,
           "Minter's Coin balance is non-zero"
       );
   });
 
   it("should check second account balance", async () => {
       let balance = await instance.balances(accounts[1]);
       assert.equal(
           balance.valueOf(),
           0,
           "Second Account Coin balance is non-zero"
       );
   });
 
   it("should mint 1000 Coins to second account", async () => {
       await instance.mint(accounts[1], amount, { from: owner });
       let balance = await instance.balances(accounts[1]);
       assert.equal(
           balance.valueOf(),
           amount,
           "Second Account's Coin balance is not equal to the minting amount"
       );
   });
 
   it("should throw if mint is called not from minter account", async () => {
       try {
           await instance.mint(accounts[2], amount, { from: accounts[1] });
       } catch (error) {
           assert.throws(() => { throw new Error(error) }, Error, "Unauthorized Access");
       }
   });
 
   it("should transfer 500 Coins from third account to second account", async () => {
       try {
           await instance.send(accounts[1], transferAmount, { from: accounts[2] });
       } catch (error) {
           assert.throws(() => { throw new Error(error) }, Error, "Insufficient balance");
       }
   });
 
   it("should transfer 500 Coins from second account to third account", async () => {
       await instance.mint(accounts[1], amount, { from: owner });
       await instance.send(accounts[2], transferAmount, { from: accounts[1] });
       let secondAccBalance = await instance.balances(accounts[1]);
       let thirdAccBalance = await instance.balances(accounts[2]);
       assert.equal(
           secondAccBalance.valueOf(),
           amount - transferAmount,
           "Second Account's Coin balance is not equal to transfer amount"
       );
       assert.equal(
           thirdAccBalance.valueOf(),
           transferAmount,
           "Third Account's Coin balance is not equal to transfer amount"
       );
   });
});


Running Test File:

Truffle provides a clean-room environment for running the test cases. When running your tests against Ganache or Truffle Develop, Truffle will use advanced snapshotting features to ensure that test files don’t share state with each other. When running against other Ethereum clients like go-ethereum, Truffle will re-deploy all of your migrations at the beginning of every test file to ensure you have a fresh set of contracts to test against.

We will be using truffle develop to run the test cases; one can connect to any of the ethereum networks using truffle console. Or one can run the truffle test command as well, by defining the connection details in the truffle config file.
Start by opening the develop console using the command:

$ truffle develop

It will start the ethereum node at http://127.0.0.1:9545 port and list the default 10 accounts along with the private keys in the console. To run the test file either you can simply run,

> test

or to run the specific test file you can run

> test /path/to/test/file (In our case, run test /test/coinTest.js)

The result is shown below:

This blog defines a simple procedure that one can follow to test their smart contracts. Balance project efforts, costs and timeline between development and testing your contract, because remember “All code is guilty until proven innocent”.

About the Author