100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Testing Contracts with Hardhat

Learn how to write reliable automated tests for Solidity contracts using Hardhat's Mocha, Chai, and ethers.js stack, including fixtures, coverage, and gas reporting.

Practical SolidityIntermediate10 min readJul 10, 2026
Analogies

Why Test Smart Contracts

Smart contracts are immutable once deployed: unlike a web server you can patch and redeploy in minutes, a bug in a live contract can permanently lock or drain user funds, and there is no undo button. Testing is therefore not optional quality assurance but a core part of writing correct contracts. Hardhat lets you run your contracts against a local, instant, in-process Ethereum network so you can exercise every function, revert path, and edge case thousands of times before spending real gas on a public network.

🏏

Cricket analogy: It mirrors a DRS review before a batter finally walks off — once the umpire's decision is locked and the player leaves the field, it can't be reversed, so teams verify every marginal LBW call before it becomes final.

The Hardhat Testing Stack

Hardhat's default testing stack combines the Mocha test runner, the Chai assertion library, and the ethers.js library for interacting with contracts. The @nomicfoundation/hardhat-chai-matchers plugin adds Solidity-aware assertions such as expect(...).to.be.revertedWith('Not owner'), .to.emit(contract, 'Transfer').withArgs(...), and .to.changeEtherBalance(...). You deploy a contract inside a test with ethers.getContractFactory and factory.deploy(), then call its functions and assert on return values, emitted events, reverts, and resulting on-chain state, all against Hardhat's fast in-memory network.

🏏

Cricket analogy: It's like a fielding side using specialist tools together, Hawk-Eye for trajectory, Snickometer for edges, Hot Spot for contact, where each assertion matcher checks one specific kind of outcome precisely.

javascript
const { expect } = require("chai");
const { ethers } = require("hardhat");
const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers");

describe("Vault", function () {
  async function deployVaultFixture() {
    const [owner, attacker] = await ethers.getSigners();
    const Vault = await ethers.getContractFactory("Vault");
    const vault = await Vault.deploy();
    return { vault, owner, attacker };
  }

  it("accepts deposits and emits an event", async function () {
    const { vault, owner } = await loadFixture(deployVaultFixture);
    await expect(vault.deposit({ value: ethers.parseEther("1.0") }))
      .to.emit(vault, "Deposit")
      .withArgs(owner.address, ethers.parseEther("1.0"));
    expect(await vault.balanceOf(owner.address)).to.equal(ethers.parseEther("1.0"));
  });

  it("blocks withdrawals from non-owners", async function () {
    const { vault, attacker } = await loadFixture(deployVaultFixture);
    await expect(vault.connect(attacker).withdrawAll())
      .to.be.revertedWith("Not owner");
  });
});

Fixtures and Test Isolation

Re-deploying contracts and re-seeding state at the start of every it block is slow and error-prone. Hardhat's loadFixture helper solves this: the first time it runs a fixture function it executes the setup and takes a snapshot of the blockchain state; every subsequent call instantly reverts the network to that snapshot instead of re-running the code. This keeps tests fast and, crucially, fully isolated, so no test can leak state into another and a deposit made in one test never accidentally affects the balance asserted in the next.

🏏

Cricket analogy: It's like rolling and re-marking the pitch to identical conditions before each innings, so one team's play never leaves the surface altered for the next, giving a clean, repeatable baseline.

loadFixture is provided by @nomicfoundation/hardhat-network-helpers. Only use it for setup that does not depend on the current block state, since it always reverts to the first snapshot. Its speed comes from the evm_snapshot / evm_revert JSON-RPC methods on Hardhat's local node.

Coverage and Gas Reporting

Two plugins turn passing tests into measurable quality signals. solidity-coverage instruments your contracts and reports the percentage of lines, branches, and functions your tests actually execute, exposing untested revert paths and unreached branches that are exactly where bugs hide. hardhat-gas-reporter prints the gas cost of each function call and deployment as your suite runs, so you can catch expensive regressions and compare optimizations. High coverage does not prove correctness, since you can execute a line without asserting anything meaningful about it, but low coverage reliably proves you have blind spots.

🏏

Cricket analogy: It's like a wagon-wheel chart showing which parts of the ground a batter has scored in, where gaps reveal untested shot regions, though hitting an area is not the same as scoring well there.

Do not treat a green test suite or 100% coverage as proof of correctness. Coverage only measures which code ran, not whether your assertions checked the right thing. Always assert on concrete outcomes, events, reverts, and state, and deliberately test malicious and edge-case inputs, not just the happy path.

  • Contracts are immutable once deployed, so thorough testing before deployment is essential, not optional.
  • Hardhat's stack is Mocha (runner) + Chai (assertions) + ethers.js (contract interaction) on a fast in-process network.
  • hardhat-chai-matchers adds Solidity-aware assertions like revertedWith, emit/withArgs, and changeEtherBalance.
  • loadFixture snapshots setup state and reverts to it between tests, keeping them fast and isolated.
  • connect(signer) sends transactions from a specific account, letting you simulate attackers and access-control checks.
  • solidity-coverage measures line/branch/function coverage; low coverage proves blind spots, high coverage does not prove correctness.
  • hardhat-gas-reporter surfaces per-function and deployment gas costs so you can catch regressions and validate optimizations.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#SolidityStudyNotes#TestingContractsWithHardhat#Testing#Contracts#Hardhat#Test#StudyNotes#SkillVeris#ExamPrep