My Image
CoursesQuizzesProblemsContestsSmartBooks
Contest!

No results found

LOGINREGISTER
My ProgressCoursesQuizzesProblemsContestsSmartbooks
Published on 19 Dec 2022
Hardhat Testing Guide
Learn how to test smart contracts using Hardhat framework (GITHUB REPO INCLUDED)
img
Mohammad Ayaan
0
Like
716

img

What is Hardhat?

Hardhat is a development environment for Ethereum applications. It provides a set of tools and libraries for building, testing, and deploying Ethereum applications. Hardhat is built on top of the Solidity compiler and the Web3.js library, and it integrates with popular testing frameworks such as Mocha and Chai.

 

Features of Hardhat

One of the main features of Hardhat is its ability to easily spin up a local Ethereum blockchain for testing purposes. This allows developers to test their applications in a realistic environment without the need for a live blockchain.

Hardhat also provides a number of utility functions for deploying and interacting with smart contracts, as well as tools for working with the Ethereum Virtual Machine (EVM). Overall, Hardhat is a powerful and flexible tool for Ethereum development that can help developers build and test their applications more efficiently.

 

Testing Smart Contracts with Hardhat

Testing smart contracts is an essential part of the development process, as it helps ensure that the contracts are working as intended and are free of errors. Hardhat is a development environment for Ethereum applications that provides a range of tools and libraries for testing smart contracts.

Testing smart contracts with Hardhat has a number of benefits, including:

  1. Speed: Hardhat allows developers to spin up a local Ethereum blockchain for testing purposes, which means tests can be run much faster than on a live blockchain.
  2. Cost: Testing on a local blockchain means developers don't have to pay for real transactions on the live blockchain, which can save significant amounts of money.
  3. Control: Testing on a local blockchain gives developers complete control over the testing environment, including the ability to reset the blockchain at any time.
  4. Integration: Hardhat integrates with popular testing frameworks such as Mocha and Chai, which makes it easy for developers to write and run tests for their smart contracts.
  5. Flexibility: Hardhat provides a range of tools and libraries for testing smart contracts, which allows developers to choose the testing approach that best fits their needs.

Overall, testing smart contracts with Hardhat is a fast, cost-effective, and flexible way to ensure that your contracts are working as intended.

Installing Hardhat

To install hardhat and create a project, you will need Node.js and an IDE to run and tests your smart contracts, preferably VS CODE.\

  • Download Node.js
  • Download VS Code

After downloading and installing both of them, open your terminal and type the below command to install hardhat

npm install -g hardhat

 

CREATING NEW HARDHAT PROJECT

  • Create a new folder for your project.
  • Open your folder on vs code and type the following command in VS CODE terminal to setup hardhat init
npx hardhat
  • After writing this command, you will see a window like thisimg
  • Select Javascript project and click ENTER
  • After setup is complete, Hardhat will ask you to INSTALL dependencies, Just copy-paste them on the terminal and install them.
  • imgOnce the installation is complete, we can continue with our testing process 😊😊

 

STRUCTURE OF HARDHAT FRAMEWORK

The hardhat framework consists of the following folders:-

img

The default folder structure of a Hardhat project is as follows:

  1. contracts/: This folder contains the Solidity source code for the project's contracts.
  2. test/: This folder contains the JavaScript test files for the project.
  3. hardhat.config.js: This is the main configuration file for the Hardhat project. It specifies various options, such as the Ethereum network to use for deployment, the Solidity compiler to use, and the test runner to use.
  4. package.json: This file contains metadata about the project, such as the project's name, version, dependencies, and scripts.
  5. .gitignore: These consists file and folders that will be ignored by the git
  6. scripts/: This folder consists of deploy scripts used to deploy smart contracts using hardhat (we will see them in another guide soon!)

 

1) CREATING SMART CONTRACT

The first step, before writing testing scripts will be to write smart contract which we will test. I will take a very basic smart contract so that even beginners can follow but will include all sort of testable parameters like events, and requirements in it.

  • Go to the contracts folder, rename the given solidity file as Value.sol.
  • Write the following smart contract in that file (or paste it, but I will encourage understanding and writing)
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ValueContract {
    uint public balance;
    address public owner;
    event AddValue(uint value, uint balance);
    event SubValue(uint value, uint balance);

    constructor(uint _balance) {
        balance = _balance;
        owner = msg.sender;
    }

    function add(uint _value) public {
        // this function will ADD value to the balance
        balance += _value;
        emit AddValue(_value, balance);
    }

    function subtract(uint _value) public {
        // this function will SUBTRACT value from the balance ONLY IF it is called by the owner
        require(msg.sender == owner, "You are not the owner");
        balance -= _value;
        emit SubValue(_value, balance);
    }

    function getOwner() public view returns (address) {
        // this function will return the owner
        return owner;
    }
}

Let's break down the above code for you.

The contract has the following variables and functions:

  • balance: a public variable of type uint, which stands for unsigned integer. This variable represents the current balance of the contract.
  • owner: a public variable of type address, which represents the Ethereum address of the contract owner.
  • AddValue: an event that is triggered when the add function is called. The event has two parameters: value and balance, both of type uint.
  • SubValue: an event that is triggered when the subtract function is called. The event has two parameters: value and balance, both of type uint.

img

  • constructor: a special function that is called when the contract is deployed. The constructor has one parameter: _balance, which represents the initial balance of the contract. Inside the constructor, the balance variable is set to the value of _balance, and the owner variable is set to the address of the contract creator.

img

  • add: a public function that adds a value to the contract's balance. The function has one parameter: _value, which represents the value to be added. The function increases the value of the balance variable by the value of _value, and then triggers the AddValue event.

img

  • subtract: a public function that subtracts a value from the contract's balance. The function has one parameter: _value, which represents the value to be subtracted. The function first checks that the caller is the owner of the contract using a require statement. If the caller is not the owner, the function throws an error. If the caller is the owner, the function decreases the value of the balance variable by the value of _value, and then triggers the SubValue event.

img

  • getOwner: a public view function that returns the contract's owner address. The function has no parameters and returns an address type.

img

 

 

2) WRITING THE TESTING SCRIPT

Let's start writing our TESTING SCRIPT.

Let's call dependencies which we will use during the testing

const { expect } = require("chai");
const { ethers } = require("hardhat");
const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers");
  • Chai:- Chai is used in testing the contracts.
  • Ethers:- Ethers will be used in deploying the contracts
  • loadFixture:- It is a setup function that is run only the first time it's invoked. On subsequent invocations, instead of re-running it, Hardhat will reset the state of the network to what it was at the point after the fixture was initially executed.

 

Let's describe our contract and write the deployment function that we will call again and again

describe("ValueContract", () => {
  async function deployFunction() {
    // we will call this function in our tests to deploy a new contract and add an owner
    let contractFactory = await ethers.getContractFactory("ValueContract");
    let contract = await contractFactory.deploy(100);
    // (await contract).deployed();
    const [owner, otherAccount] = await ethers.getSigners();
    return { contract, owner, otherAccount };
  }
  deployFunction();

The code is defining a describe block for a group of tests related to a contract called ValueContract. Within this block, it defines an async function called deployFunction.

The deployFunction function does the following:-

  • Gets the contract factory for a contract called "ValueContract" using ethers.getContractFactory("ValueContract").
  • Deploys a new instance of the "ValueContract" contract using contractFactory.deploy(100).
  • Gets the owner and another account using ethers.getSigners().
  • Returns an object containing the contract instance, owner, and other account.
  • Finally, the deployFunction function is called at the end of the code block.

 

LET's START OUR FIRST TEST

We will write script to test our add() function in our smart contract. The function should take a value and add that to our balance

it("should add value to balance", async () => {
    const { contract } = await loadFixture(deployFunction);
    await contract.add(50);
    const balance = await contract.balance();
    expect(balance.toNumber()).to.equal(150);
  });

This code defines an it block, which is a single test within the describe block for the ValueContract contract.

The test does the following:

  • Calls the loadFixture function with the deployFunction function as an argument, and destructures the returned object to get the contract property.
  • Calls the add method on the contract instance with the argument 50.
  • Calls the balance method on the contract instance and assigns the returned value to the balance variable.
  • Uses the expect function from the chai library to assert that the balance.toNumber() is equal to 150.


In summary, this test is checking that the add method of the ValueContract contract correctly increments the contract's balance by the specified amount.


TESTING

Go to your terminal and type:-

 

npx hardhat test

 

You will see the following result:-

img

Congratulations, you have successfully performed your first test and passed it too!!!!

let's move ahead

Let's write our second test 

We will write scripts to test the subtract() function that we declared in the smart contract.

*Note that this subtract function can only be called by the owner

 it("should subtract value from balance", async () => {
    const { contract, owner } = await loadFixture(deployFunction);
    await contract.subtract(25, { from: owner.address });
    const balance = await contract.balance();
    expect(balance.toNumber()).to.equal(75);
  });

The test does the following:

  • Calls the loadFixture function with the deployFunction function as an argument, and destructures the returned object to get the contract and owner properties.
  • Calls the subtract method on the contract instance with the argument 25, and specifies the from property in the options object as the owner.address.
  • Calls the balance method on the contract instance and assigns the returned value to the balance variable.
  • Uses the expect function from the chai library to assert that the balance.toNumber() is equal to 75.

In summary, this test is checking that the subtract method of the ValueContract contract correctly decrements the contract's balance by the specified amount when called by the owner.

TESTING

Go to your terminal and type:-

npx hardhat test

You will see the following result:-

imgThis means BOTH your tests are passing.

 

Third Test

We will now write test again for our subtract() function but this time we will call it from ANOTHER account (NOT OWNER ACCOUNT) to verify that the require function is working

 it("should not allow non-owners to subtract value from balance", async () => {
    const { contract, otherAccount } = await loadFixture(deployFunction);
    await expect(
      contract.connect(otherAccount).subtract(25)
    ).to.be.rejectedWith("You are not the owner");
  });

The test does the following:

  • Calls the loadFixture function with the deployFunction function as an argument, and destructures the returned object to get the contract and otherAccount properties.
  • Calls the connect method on the contract instance with the otherAccount as an argument, and then calls the subtract method on the returned contract instance with the argument 25.
  • Uses the expect function from the chai library with the to.be.rejectedWith chai function to assert that this function call will be rejected with the error message "You are not the owner".

In summary, this test is checking that the subtract method of the ValueContract contract will not allow non-owners to decrement the contract's balance.

TESTING

Go to your terminal and type:-

npx hardhat test

You will see the following result:-

imgThis means all three of your tests are passing successfully

 

Fourth Test

Let's write another script to test whether the ADDVALUE event that we defined is getting triggered or not when we call the add() function.

 it("should emit ADDVALUE event", async () => {
    const { contract, owner } = await deployFunction();
    await contract.add(50);
    const balance = await contract.balance();
    expect(balance.toNumber()).to.emit(50, balance, "ADDVALUE");
  });

The test does the following:

  • Calls the loadFixture function with the deployFunction function as an argument, and destructures the returned object to get the contract and owner properties.
  • Calls the add method on the contract instance with the argument 50, and specifies the from property in the options object as the owner.address.
  • Calls the balance method on the contract instance and assigns the returned value to the balance variable.
  • Uses the expect function from the chai library to assert that the balance.toNumber() is emitting the ADDVALUE event in the smart contract

In summary, this test is checking that the ADDVALUE event of the ValueContract contract will be triggered when add function is called.

 

TESTING

Go to your terminal and type:-

npx hardhat test

You will see the following result:-

img

 This means all four of your tests are passing successfully

 

Fifth Test

Let's write another script to test whether the ADDVALUE event that we defined is getting triggered or not when we call the add() function.

 it("should emit SUBVALUE event", async () => {
    const { contract, owner } = await deployFunction();
    await contract.subtract(25, { from: owner.address });
    const balance = await contract.balance();
    expect(balance.toNumber()).to.emit(25, balance, "SUBVALUE");
  });

The test does the following:

  • Calls the loadFixture function with the deployFunction function as an argument, and destructures the returned object to get the contract and owner properties.
  • Calls the subtract method on the contract instance with the argument 25, and specifies the from property in the options object as the owner.address.
  • Calls the balance method on the contract instance and assigns the returned value to the balance variable.
  • Uses the expect function from the chai library to assert that the balance.toNumber() is emitting the SUBVALUE event in the smart contract

In summary, this test is checking that the SUBVALUE event of the ValueContract contract will be triggered when add function is called.

TESTING

Go to your terminal and type:-

npx hardhat test

You will see the following result:-

img

 This means all five of your tests are passing successfully

 

Final Test

Let's check if our getOwner() function is returning the owner

  it("should return the owner", async () => {
    const { contract, owner } = await deployFunction();

    expect(await contract.getOwner()).to.equal(owner.address);
  });

The test does the following:

  • Calls the loadFixture function with the deployFunction function as an argument, and destructures the returned object to get the contract and owner properties.
  • Uses the expect function from the chai library to assert that the contract.getOwner() is equal to the owner address that we defined above.

In summary, this test is checking that the getOwner() event of the ValueContract contract will be returning the owner correctly.

 

TESTING

Go to your terminal and type:-

npx hardhat test


You will see the following result:-

img

 

 This means ALL of your tests are passing successfully

 

HURRAY!! YOU HAVE LEARNT HOW TO TEST SMART CONTRACTS USING HARDHAT 🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳

 

A RECAP OF WHAT YOU LEARNT

  • How to test Smart contracts FUNCTIONS
  • How to test Smart Contracts EVENTS
  • How to test Smart Contracts RESTRICTIONS

GITHUB REPO LINK

Here is the link:- https://github.com/moayaan1911/hardhat-testing-dapp-world

Where to go from here?

Make you you code more smart contracts and test them via hardhat. TESTING is the MOST IMPORTANT part of dapp development. I am saying it again. All the DeFi hacks are happening because of bugs in smart contracts so as a developer you need to make sure that you test your smart contracts for any bugs if possible.

 

CONNECT WITH THE WRITER

Hello reader, myself MOHAMMAD AYAAN SIDDIQUI from India. I hope you liked the guide. Thank you 🙏🙏 for reading this and if you want a "VIDEO" version of the same, let me know 👍👍

If you want to connect with me, you can find ALL my socials on my linktree:-

https://linktr.ee/ayaaneth

 

 

Enjoyed the SmartBook?
Like
logo
contact@dapp-world.com
Katraj, Pune, Maharashtra, India - 411048

Follow Us

linkedintwitteryoutubediscordinstagram

Products

  • SmartBooks
  • Courses
  • Quizzes
  • Assessments

Support

  • Contact Us
  • FAQ
  • Privacy Policy
  • T&C

Backed By

ah! ventures

Copyright 2023 - All Rights Reserved.

Recommended from DAppWorld
img
1 May 2021
How to connect Ganache with Metamask and deploy Smart contracts on remix without
Set up your development environment with (Metamask + Ganache + Remix) and skip truffle :)
3 min read
11509
5
img
8 Jul 2021
How to interact with smart contarct from backend node js
call and send functions from backend server side using nodejs
3 min read
8103
2
img
18 Aug 2021
Send transaction with web3 using python
Introduction to web3.py and sending transaction on testnet
3 min read
6229
5
img
5 Aug 2021
Deploy Smart Contract on Polygon POS using Hardhat
how to deploy smart contracts on polygon pos chain using hardhat both mainnet and testnet ?
3 min read
5540
3