How to Write Tests for Solidity Smart Contracts Using Web3 and Mocha

Introduction

In this article I will show you how to test your Solidity smart contracts by writing tests in javascript using ganache, web3, and mocha. Writing tests for your code is one of the most important skills a developer can have. It is even more important for a blockchain developer because once you deploy your smart contracts to the Ethereum blockchain for the most part they can not be changed. This means that if you deploy buggy code to the Ethereum blockchain you will have to redeploy your entire smart contract in order to fix this bug. This will requires users on your app to switch over to the new contract and potentially abandon information they stored on your old one.

Setup

Before you begin you will need to have Nodejs installed and have your compiled smart contracts stored somewhere in your project. If you are unsure on how to compile your smart contracts check out my tutorial here. Next we are going to install the necessary dependencies.

npm install --save ganache-cli web3 mocha

Once these libraries are installed create a new directory in your top level project directory called test/ and create a file called ContractName.test.js inside of your new directory. For this tutorial I will be calling my test file Greeting.test.js.

Test File

To get our test file running the first thing we have to do is import the necessary dependencies.

const assert = require('assert');
const ganache = require('ganache-cli');
const Web3 = require('web3');
// This is my path to my compiled smart contract yours may be different
const compiledGreeting = require('../ethereum/build/Greeting.json');

Before we go any further I want to give an overview on what each library does. The assert library verifies parts of our code to ensure they are working correctly. We will use assert combined with our testing framework mocha to test our smart contracts. The ganache-cli library is the command line interface (cli) version of ganache. This library creates your own personal Ethereum test network right on your computer with a default of 10 accounts loaded with fake ether. This makes it very convient and efficient for us to test our smart contracts are working correctly. The web3 library allows us to interact with a local or remote Ethereum node (In our case local). Using web3 we can deploy our smart contracts to the Ethereum blockchain and interact with them, such as call functions on the contracts. The ../ethereum/build/Greeting.json is our compiled smart contract using JSON, which we will use to deploy our smart contract to our local Ethereum node. The next step is to initialize our web3 instance.

const web3 = new Web3(ganache.provider());

This code initializes our instance of web3 and tells it that it will be interacting with the ganache Ethereum node on our local computer. Next we are going to initialize an instance of our compiled contract.

// This is the path to my compile contract in JSON yours may be different.
const compiledGreeting = require('../ethereum/build/Greeting.json');

The compiledGreeting object contains the information we pass to web3 for web3 to be able to interact with our contract on the Ethereum blockchain. Next we are going to define our mocha beforeEach function which will run before each of our mocha tests.

let accounts;
let greeting.

beforeEach(async () => {
    // Grabs the accounts that ganache creates
    accounts = await web3.eth.getAccounts();
    // Create greeting
    greeting = await new web3.eth.Contract(compiledGreeting.abi)
        .deploy({ data: compiledGreeting.evm.bytecode.object })
        .send({ from: accounts[0], gas: '3000000' });
});

This code declares two variables accounts and greeting these variables are initialized in the beforeEach function which mocha automatically runs before each of our tests which we will define later on. This saves us from having to write this code for each of our possibly many tests. The first thing we do in our function does is grab the accounts that ganache automatically creates for us. We then create a javascript object of our Solidity smart contract with the line greeting = await new web3.eth.Contract(compiledGreeting.abi) this will allow us to exchange information with our deployed smart contracts in javascript. Then we deploy the contract to our local ganache Ethereum blockchain with the lines

.deploy({ data: compiledGreeting.evm.bytecode.object })
.send({ from: accounts[0], gas: '3000000' });

The deploy function tells ganache the bytecode for our smart contract to execute. The send function actually sends our smart contract code to run on ganache. You can find more information about these functions here. Next we will write the code that actually tests our smart contracts.

describe('Greeting', () => {
    it('deploys a greeting contract', () => {
        assert.ok(greeting.options.address);
    });
    it('sets and gets the stored string', async () => {
        // Set the greeting string
        await greeting.methods.setGreeting("hello world")
            .send({ from: accounts[0], gas: '3000000' });
        // Get the greeting string
        const greetingStr = await greeting.methods.getGreeting().call();
        assert.strictEqual(greetingStr, "hello world");
    });
});

The describe function essentially groups together the tests we want to run on our Greeting smart contract. We then define the inividual tests inside with mocha’s it function. The first test simply checks if our smart contract was deployed to ganache by checking that the contract has a valid address. Remember that every smart contract on the Ethereum blockchain has an address associated with it as an identifier. The second test checks that we can set and get the greeting string stored in the contract. The code below shows the generic way to call our Solidity smart contract functions from javascript.

await contractObject.methods.solidityFunctionName(parameters).call();
or
await contractObject.methods.solidityFunctionName(parameters)
    .send({ from: accounts[0], gas: '3000000');

You can see we access the methods object on our smart contract object and then we call the specific Solidity function with the given parameters. If our Solidity function is constant/view and does not modify any data we can call the function with the .call() method as shown in the first example. If our Solidity function changes or stores data we have to send along additional information such as who is calling the function and how much money are they willing to pay for gas. We can see these two examples in our own code as our getGreeting() function does not modify any data, but our setGreeting() function does. The last line of code in our second test tests that the returned greeting string from our smart contract is what we set it to with setGreeting(). We can run our tests using the mocha command from our root directory.

mocha
  Greeting
    ✔ deploys a greeting contract
    ✔ sets and gets the stored string (127ms)

  2 passing (475ms)

You should see the output above or something similar if you wrote different tests.

You can view the entire file below.

const assert = require('assert');
const ganache = require('ganache-cli');
const Web3 = require('Web3');
const web3 = new Web3(ganache.provider());
const compiledGreeting = require('../ethereum/build/Greeting.json');

let accounts;
let greeting;

beforeEach(async () => {
    accounts = await web3.eth.getAccounts();

    // Create greeting
    greeting = await new web3.eth.Contract(compiledGreeting.abi)
        .deploy({ data: compiledGreeting.evm.bytecode.object })
        .send({ from: accounts[0], gas: '3000000' });
});

describe('Greeting', () => {
    it('deploys a greeting contract', () => {
        assert.ok(greeting.options.address);
    });
    it('sets and gets the stored string', async () => {
        // Set the greeting string
        await greeting.methods.setGreeting("hello world")
            .send({ from: accounts[0], gas: '3000000' });
        // Get the greeting string
        const greetingStr = await greeting.methods.getGreeting().call();
        assert.strictEqual(greetingStr, "hello world");
    });
});

Conclusion

In this tutorial I showed you how to write tests for your Solidity smart contracts using javascript and mocha. Hopefully this gave you some insight on how you can test your smart contracts to ensure they are reliable before deploying.

If you have any suggestions for artices or feedback on this article let me know in the comments!