Hardhat Testing – How to Mock Smart Contract Functions for Testing in Hardhat

etherjshardhatmockwaffle

In my Test I want to mock an external function that I call inside my smart contract to test both conditions without writing and deploying an extra fake smart contract.

My Contract

//SPDX-License-Identifier: Unlicense
pragma solidity 0.8.4;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
 
contract Greeter {
    uint256 private number = 0;
    constructor() {}
    function getNumber() public view returns (uint256) {
        return number;
    }
    // if transferFrom returns true then set number to 1 , else set number to 2
    function setNumber(IERC20 erc20Address) public {
        erc20Address.transferFrom(msg.sender, address(this), 10**18) ? number = 1 : number = 2;
    }
}

The external contract

//SPDX-License-Identifier: Unlicense
pragma solidity 0.8.4;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MyERC20 is ERC20 {
    constructor() ERC20("Name", "M-T") {}
}

The function to mock is transferFrom

Best Answer

make sure you have installed the extra packages npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers and then use Waffle for that.

waffle provides a deployMockContract method to create an instance of the contract that can be mocked, and then its functions can be mocked with <mocked-instance>.mock.<function-name>.returns(<values..>)

https://ethereum-waffle.readthedocs.io/en/latest/mock-contract.html#mock-contract

For my 2 smart contract the test will look like this:


const { expect } = require("chai");
const { waffle, ethers } = require('hardhat');
const { deployMockContract, provider } = waffle;


describe("Test", function () {

  it('should test both conditions of the setNumber function', async () => {
    const [deployerOfContract] = provider.getWallets();
    // deploy the contract to Mock
    const MyERC20 = require('../artifacts/contracts/MyERC20.sol/MyERC20.json');
    const mockedMyERC20 = await deployMockContract(deployerOfContract, MyERC20.abi);

    // deploy the Greeter contract
    const Greeter = await ethers.getContractFactory('Greeter');
    const greeterInstance = await Greeter.deploy();

    // mock the function transferFrom -> return always true
    await mockedMyERC20.mock.transferFrom.returns(true);
    // call setNumber
    await greeterInstance.setNumber(mockedMyERC20.address);

    let storedNumber = await greeterInstance.getNumber();
    expect(storedNumber.toString()).to.equal('1');

    // mock the function transferFrom -> return always false
    await mockedMyERC20.mock.transferFrom.returns(false);
    // call setNumber
    await greeterInstance.setNumber(mockedMyERC20.address);
    storedNumber = await greeterInstance.getNumber();
    expect(storedNumber.toString()).to.equal('2');
  });

});

and then run

  • npx hardhat compile
  • npx hardhat test

result

  Test
    ✔ should test both conditions of the setNumber function (858ms)
  1 passing (1s)
Related Topic