Automating Contract Tests with Hardhat Network

Automating Contract Tests with Hardhat Network

In the fast-paced world of Ethereum development, robust automated testing is non-negotiable. Hardhat Network provides an ideal local Ethereum environment, enabling developers to write, run and automate contract tests rapidly. This article delves deep into how to leverage Hardhat Network to create a comprehensive, automated testing workflow that integrates seamlessly into your CI/CD pipeline.

1. The Essentials: Hardhat Its Network

Hardhat is a development environment for Ethereum smart contracts, offering key features such as Solidity compilation, network forking and a built-in local network. The Hardhat Network is a local Ethereum node designed for tests, allowing instant transactions mining, advanced logging, snapshots, time manipulation and more. Together, they form a powerful duo for contract testing automation.

1.1 Why Hardhat Network

  • Instant Mining: Transactions are mined immediately—no waiting for block confirmations.
  • Blockchain Snapshots: Create and revert to snapshots within tests to isolate state.
  • Time Travel: Advance or rewind block timestamps to simulate time-based logic.
  • Forking Mainnet: Fork live networks for realistic integration testing against actual contracts and state.

2. Setting Up Your Hardhat Project

Initialize a new Hardhat project:

npx hardhat init
  

Install test libraries:

npm install --save-dev @nomiclabs/hardhat-ethers ethers chai mocha @nomiclabs/hardhat-waffle
  

Configure hardhat.config.js:

module.exports = {
  solidity: 0.8.19,
  networks: {
    hardhat: {
      chainId: 1337,
      forking: {
        url: process.env.ALCHEMY_MAINNET_URL,
        blockNumber: 17000000,
      }
    }
  }
}
  

3. Writing Automated Contract Tests

Hardhat uses Mocha as the test runner and Chai or Waffle for assertions. Below is a pattern for structuring tests with fixtures, snapshots and time manipulation:

3.1 Sample Test Suite

const { expect } = require(chai)
const { ethers, network } = require(hardhat)

describe(MyToken Contract, function () {
  let Token, token, owner, addr1

  before(async function () {
    Token = await ethers.getContractFactory(MyToken)
    [owner, addr1] = await ethers.getSigners()
  })

  beforeEach(async function () {
    // Snapshot to revert state after each test
    this.snapshotId = await network.provider.request({
      method: evm_snapshot,
      params: [],
    })
    token = await Token.deploy(MyToken, MTK, 18, ethers.utils.parseUnits(1000))
    await token.deployed()
  })

  afterEach(async function () {
    // Revert to snapshot
    await network.provider.request({
      method: evm_revert,
      params: [this.snapshotId],
    })
  })

  it(should mint tokens to owner, async function () {
    const balance = await token.balanceOf(owner.address)
    expect(balance).to.equal(ethers.utils.parseUnits(1000))
  })

  it(should transfer tokens correctly, async function () {
    await token.transfer(addr1.address, ethers.utils.parseUnits(100))
    const balance1 = await token.balanceOf(addr1.address)
    expect(balance1).to.equal(ethers.utils.parseUnits(100))
  })
})
  

4. Advanced Automation Techniques

4.1 Time Manipulation

Testing time-locked functions requires moving the blockchain clock.

// Increase time by 1 day (86400 seconds)
await network.provider.send(evm_increaseTime, [86400])
await network.provider.send(evm_mine)
  

4.2 Forking Integration Tests

Fork Ethereum mainnet to test interactions with real contracts:

hardhat.config.js:
  forking: {
    url: process.env.ALCHEMY_MAINNET_URL,
    blockNumber: 17000000
  }
  

Now you can impersonate accounts and call existing contracts:

await network.provider.request({
  method: hardhat_impersonateAccount,
  params: [richHolderAddress],
})
const signer = await ethers.getSigner(richHolderAddress)
  

5. Integrating Tests into CI/CD

Automate test runs on every push using GitHub Actions, GitLab CI or CircleCI. Example GitHub Action workflow:

name: CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 18
      - run: npm ci
      - run: npx hardhat test --network hardhat
  

6. Plugins Ecosystem

Enhance your testing setup with these Hardhat plugins:

Plugin Purpose Link
hardhat-deploy Manage complex deployments and fixtures GitHub
hardhat-gas-reporter Report gas usage in tests GitHub
solidity-coverage Generate test coverage reports GitHub

7. Best Practices Troubleshooting

  • Isolate Tests: Use evm_snapshot / evm_revert liberally.
  • Use Fixtures: Pre-deploy contracts once per suite to speed up tests (Hardhat Waffle Guide).
  • Seed Randomness: Avoid randomness in tests set fixed block timestamps and nonces.
  • Monitor Gas: Integrate hardhat-gas-reporter to detect regressions.
  • CI Secrets: Store RPC endpoints and private keys in encrypted environment variables.

Conclusion

Automating contract tests with Hardhat Network accelerates development by providing a deterministic, feature-rich Ethereum environment. By leveraging snapshots, time control, network forking and a rich plugin ecosystem, you can build a CI-driven workflow that catches issues early and ensures production-grade reliability.

For further reading, explore the official Hardhat documentation: Getting Started with Hardhat, Hardhat Plugins, and deepen your testing expertise with the Ethereum Developer Resources.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *