How to Test Smart Contracts – Tutorial

Testing a smart contract is a must of the blockchain development process.
Remember that the blockchain does not forgive you any errors because of its immutability!

The only way to fix a bug on a deployed smart contract is to deploy a new version of that contract; the old version with the bug will be still on the blockchain and it will remain there forever.

So testing the smart contract before deploying it will make sure your functions will have the expected behavior.
Luckily, the truffle framework makes the testing process very easy. In this tutorial we are going to use Mocha to test our smart contracts.

We are going to test the smart contract created on the tutorial Create your Blockchain DApp with Ethereum and VueJS.

Below you can find the link to download the full project and the link to the test file we are going to create in this tutorial:

If you are familiar with Javascript and ethereum smart contract I think it will be easier to see straight away the test file clicking the button before. The code is well commented.

Set up the project to test

Let’s clone from the git repository the DApp developed in the previous lesson. Open the terminal and run the command:

git clone https://github.com/danielefavi/ethereum-vuejs-dapp.git

and then let’s cd in the root project folder:

cd ethereum-vuejs-dapp

Then please delete the test folder since we are going to create the test from scratch.

What do we want to test?

Before developing the test we must define what to test.
To test your smart contracts you must have a list of the functionalities and restrictions you want to be tested. In our case we are going to test:

  1. Register a user with a username and a status.
  2. A registered user can update his own profile.
  3. A user cannot be registered twice.

Set up the test

What we want to do is to test the smart contract ./contracts/User.sol. So first let’s create the folder test inside root of the truffle project:

mkdir test

Then run in the terminal the following command to create the testing file (it will create the file users.js where we are going to write our testing code):

truffle create test Users

NOTE: if you run the command truffle create test Users without creating the folder test you can get the error:

Error: ENOENT: no such file or directory, open ‘~\ethereum-vuejs-dapp\test\users.js’

Now open the file test\users.js and remove all the default content in it.

In the beginning of the file users.js we have to import in the smart contract we want to test: User.sol. To do so, we can use the code:

var Users = artifacts.require(‘./Users.sol’);

The artifacts.require() method is similar to Node’s require. In this case it returns a contract abstraction that we can use within the rest of our deployment script. The name specified should match the name of the contract definition within that source file (more info).

Then we have to use the contract() function to actually test the functions in the smart contract:

var Users = artifacts.require('./Users.sol');

contract('Users', function(accounts) {
    var mainAccount = accounts[0];

    // CODE OF THE TEST CASES HERE
});

The first parameter of contract() is the name of the smart contract and the second parameter is the callback function where you will write your test code.
Note that a list of accounts is passed to the callback function.

DEVELOPING the Test case 1
Register a user with a username and status

This test case will be executed following the steps:

  1. Register a new user using the main account: accounts[0]
  2. After the registration we are going to check the total number of registered users is increased by one.
  3. Check that the wallet address used for the registration is correctly registered.
  4. Check the username and status used for the registration matches with the username and status stored in the blockchain.

Let’s define a test case called should register a user using Mocha; then we are going to get the smart contract instance in order to be able to call the contract functions.

contract('Users', function(accounts) {
    var mainAccount = accounts[0];

    it("should register an user", function() {
        var usersBeforeRegister = null;

        return Users.deployed().then(function(contractInstance) {
            // storing the contract instance so it will be used later on
            instance = contractInstance;

            …
        });
    }); // end of "should register an user"
});

Once we got the contract instance we have to make a series of calls to the smart contract (using Promises) to:

  • Get the current number of registered users and store it in the variable usersBeforeRegister; here we are going to call the contract function totalUsers
  • Register the user using the smart contract function registerUser.
  • Get again the number of the registered user and check that the new total is increased by one compared to usersBeforeRegister.
  • Check if the wallet address used for the registration is registered.

To keep this test case simple I moved the step 4 to a dedicated test case.

This is the full code of the first test case:

it("should register an user", function() {
    var usersBeforeRegister = null;

    return Users.deployed().then(function(contractInstance) {
        // storing the contract instance so it will be used later on
        instance = contractInstance;

        // calling the smart contract function totalUsers to get the current number of users
        return instance.totalUsers.call();
    }).then(function(result) {
        // storing the current number on the var usersBeforeRegister
        usersBeforeRegister = result.toNumber();

        // registering the user calling the smart contract function registerUser
        return instance.registerUser('Test User Name', 'Test Status', {
            from: mainAccount
        });
    }).then(function(result) {
        return instance.totalUsers.call();
    }).then(function(result) {
        // checking if the total number of user is increased by 1
        assert.equal(result.toNumber(), (usersBeforeRegister+1), "number of users must be (" + usersBeforeRegister + " + 1)");

        // calling the smart contract function isRegistered to know if the sender is registered.
        return instance.isRegistered.call();
    }).then(function(result) {
        // we are expecting a boolean in return that it should be TRUE
        assert.isTrue(result);
    });
}); // end of "should register an user"

The test is passed when all the assertions within the test case are satisfied. In this case we are asserting that the number of subscribers is increased by 1 after the registration:

assert.equal(result.toNumber(), (usersBeforeRegister+1), “number of users must be (" + usersBeforeRegister + " + 1)");

and the user account is registered:

        …
        return instance.isRegistered.call();
    }).then(function(result) {
        assert.isTrue(result);
    });

To check if the username and status stored in the blockchian are correct (step 4) we can define the following test case:

it("username and status in the blockchian should be the same the one gave on the registration", function() {
    // NOTE: the contract instance has been instantiated before, so no need
    // to do again: return Users.deployed().then(function(contractInstance) { …
    // like before in "should register an user".
    return instance.getOwnProfile.call().then(function(result) {
        // the result is an array where in the position 0 there user ID, in
        // the position 1 the user name and in the position 2 the status,
        assert.equal(result[1], 'Test User Name');

        // the status is type of bytes32: converting the status Bytes32 into string
        let newStatusStr = web3.toAscii(result[2]).replace(/\u0000/g, '');
        assert.equal(newStatusStr, 'Test Status');
    });
}); // end testing username and status

This test case is passed when the username and status on the blockchain are equal to the username and status that we gave on the registration phase:

    assert.equal(result[1], 'Test User Name');
    
    let newStatusStr = web3.toAscii(result[2]).replace(/\u0000/g, '');
    assert.equal(newStatusStr, 'Test Status');

Note that the status is stored in Bytes32 so we have to convert this value to string.
Another important note: a web3 instance is available in each test file, configured to the correct provider!

DEVELOPING the Test case 2
A registered user can update his own profile

In this test case we have to check if the smart contract function that updates the user’s profile works correctly:

  • Call the smart contract function updateUser passing the new username and the new status as parameters.
  • Call the smart contract function getOwnProfile to get the user profile.
  • Check if the username and status from the result of getOwnProfile are equal to the given username and status of the first step.
it("should update the profile", function() {
    return instance.updateUser('Updated Name', 'Updated Status', {
        from: mainAccount
    }).then(function(result) {
        return instance.getOwnProfile.call();
    }).then(function(result) {
        // the result is an array where in the position 0 there user ID, in
        // the position 1 the user name and in the position 2 the status,
        assert.equal(result[1], 'Updated Name');

        // the status is type of bytes32: converting the status Bytes32 into string
        let newStatusStr = web3.toAscii(result[2]).replace(/\u0000/g, '');
        assert.equal(newStatusStr, 'Updated Status');
    });
}); // end should update the profile

DEVELOPING the Test case 3
A user cannot be registered twice

In this test case we will try to register a user that has already been registered.
Please first check the function addUser in the smart contract User.sol. The function addUser first checks if the wallet address is already registered; if the user is already registered then the registration process will be stopped:

        // checking if the user is already registered
        uint userId = usersIds[_wAddr];
        require (userId == 0);

So if we are trying to register a user twice we are expecting the registration to fail. So a failed registration means that the test is passed:

    it("a registered user should not be registered twice", function() {
        // we are expecting the call to registerUser to fail since the user account
        // is already registered!
        return instance.registerUser('Test username Twice', 'Test Status Twice', {
            from: mainAccount
        }).then(assert.fail).catch(function(error) { // here we are expecting the exception
            assert(true);
        });
    }); // end testing registration twice

LET’S RUN THE TEST

Finally let’s run the test on the console with the following command:

truffle test

this command will run all the tests in the folder test. If you want to run only a specific test file you can use the command:

truffle test .\test\users.js

If all the tests are passed, on the terminal you should see something like this:_

Test Result - Ethereum Smart Contracts

Sources

Related Articles

Create your Blockchain DApp with Ethereum and VueJS - Tutorial Part 2

Category: Blockchain

In the second part of the tutorial we are going to develop the front-end and see how to interact with the smart contract developed in the PART 1. If you missed the introduction and you want to ...

Create your Blockchain DApp with Ethereum and VueJS - Tutorial Part 1

Category: Blockchain

In this first part of the tutorial we are going to create the smart contract that handles the registration of users; then we are going to deploy the smart contract to the blockchain using ...

Create your Blockchain DApp with Ethereum and VueJS - Tutorial Intro

Category: Blockchain

This is a simple decentralized application built using Ethereum blockchain and VueJS for the front-end. This DApp example lets the users store their name and status on the ...

What is a fork? Why is it dangerous?

Category: Blockchain

Often a blockchain can have some flaws, bugs or it just needs improvements. To fix those vulnerabilities the blockchain has to be forked. A fork rewrites the rules which a full node must follow in ...

Ethereum Smart Contracts GUI Generator

Category: Blockchain

If you need to create a graphical user interface to interact with your Ethereum smart contracts, then you might be interested in the ethereum-interface-generator NPM package. The NPM package ...

Blockchain For Not Technical People (public speech)

Category: Blockchain

A 4 minutes speech about blockchain I delivered during a course on public speaking in November 2019.