Hardhat is the most popular development environment for Ethereum software. It provides tools for compiling, testing, deploying, and debugging smart contracts.
Setting Up Hardhat
Installation
bash
mkdir my-project
cd my-project
npm init -y
npm install --save-dev hardhat
npx hardhatProject Structure
my-project/
├── contracts/
│ └── MyContract.sol
├── scripts/
│ └── deploy.js
├── test/
│ └── MyContract.test.js
├── hardhat.config.js
└── .envConfiguration
javascript
// hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
module.exports = {
solidity: {
version: "0.8.19",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
networks: {
hardhat: {
chainId: 1337
},
sepolia: {
url: process.env.SEPOLIA_RPC_URL,
accounts: [process.env.PRIVATE_KEY]
}
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY
}
};Writing Tests
Basic Test
javascript
const { expect } = require("chai");
describe("MyContract", function () {
let myContract;
let owner;
beforeEach(async function () {
[owner] = await ethers.getSigners();
const MyContract = await ethers.getContractFactory("MyContract");
myContract = await MyContract.deploy();
});
it("Should set the right owner", async function () {
expect(await myContract.owner()).to.equal(owner.address);
});
it("Should deploy with initial value", async function () {
expect(await myContract.value()).to.equal(0);
});
});Testing State Changes
javascript
it("Should update value correctly", async function () {
await myContract.setValue(42);
expect(await myContract.value()).to.equal(42);
});
it("Should revert with invalid input", async function () {
await expect(
myContract.setValue(0)
).to.be.revertedWith("Value must be positive");
});Testing Events
javascript
it("Should emit Transfer event", async function () {
await expect(myContract.transfer(recipient.address, 100))
.to.emit(myContract, "Transfer")
.withArgs(owner.address, recipient.address, 100);
});Advanced Testing
Testing with Multiple Accounts
javascript
describe("Access control", function () {
it("Should only allow owner to call", async function () {
const [owner, user1] = await ethers.getSigners();
await myContract.connect(owner).adminFunction();
await expect(
myContract.connect(user1).adminFunction()
).to.be.revertedWith("Not owner");
});
});Testing Reverts
javascript
it("Should revert when insufficient balance", async function () {
await expect(
myContract.withdraw(1000)
).to.be.revertedWithCustomError(
myContract,
"InsufficientBalance"
).withArgs(1000, 100);
});Time Manipulation
javascript
it("Should work with time", async function () {
await myContract.setLockTime(3600);
// Fast forward 1 hour
await ethers.provider.send("evm_increaseTime", [3600]);
await ethers.provider.send("evm_mine");
await myContract.withdraw();
});Testing Utilities
Helper Functions
javascript
const { time, loadFixture } = require("@nomicfoundation/hardhat-network-helpers");
async function deployContractFixture() {
const [owner, user1, user2] = await ethers.getSigners();
const Contract = await ethers.getContractFactory("MyContract");
const contract = await Contract.deploy();
return { contract, owner, user1, user2 };
}
describe("MyContract", function () {
it("Should work", async function () {
const { contract, owner } = await loadFixture(deployContractFixture);
// Test
});
});Balance Assertions
javascript
it("Should transfer ETH correctly", async function () {
const [owner, recipient] = await ethers.getSigners();
await expect(() =>
contract.transfer(recipient.address, { value: ethers.parseEther("1.0") })
).to.changeEtherBalances(
[owner, recipient],
[ethers.parseEther("-1.0"), ethers.parseEther("1.0")]
);
});Gas Testing
Gas Snapshots
javascript
it("Should match gas snapshot", async function () {
const tx = await contract.function();
expect(tx).to.have.gasCost(50000);
});Gas Comparison
javascript
it("Should optimize gas", async function () {
const tx1 = await contract.expensiveFunction();
const tx2 = await contract.optimizedFunction();
expect(tx2.gasUsed).to.be.lt(tx1.gasUsed);
});Integration Testing
Mainnet Forking
javascript
it("Should work with mainnet fork", async function () {
await hre.network.provider.request({
method: "hardhat_reset",
params: [{
forking: {
jsonRpcUrl: process.env.MAINNET_RPC_URL
}
}]
});
const contract = await ethers.getContractAt(
"IERC20",
"0x6B175474E89094C44Da98b954EedeAC495271d0F" // DAI
);
const balance = await contract.balanceOf("vitalik.eth");
console.log(balance.toString());
});Coverage
Running Coverage
bash
npx hardhat coverageCoverage Thresholds
javascript
// hardhat.config.js
module.exports = {
solidity: "0.8.19",
coverage: {
threshold: {
lines: 80,
statements: 80,
branches: 80,
functions: 80
}
}
};Debugging
Console Logging
solidity
// Import hardhat console
import "hardhat/console.sol";
contract MyContract {
function debug() public {
console.log("Debug message");
console.logUint(123);
}
}Tracing
javascript
it("Should trace transaction", async function () {
const tx = await contract.function();
const receipt = await tx.wait();
console.log(receipt);
});Deployment Scripts
Simple Deploy
javascript
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying with:", deployer.address);
const Contract = await ethers.getContractFactory("MyContract");
const contract = await Contract.deploy();
await contract.deployed();
console.log("Contract deployed to:", contract.address);
}
main().catch(console.error);Verified Deploy
javascript
async function main() {
const Contract = await ethers.getContractFactory("MyContract");
const contract = await Contract.deploy();
await contract.deployed();
console.log("Contract deployed to:", contract.address);
// Wait for confirmations
await contract.deployTransaction.wait(5);
// Verify on Etherscan
await hre.run("verify:verify", {
address: contract.address,
constructorArguments: []
});
}Best Practices
- 1Write tests before or alongside contract code
- 2Test both success and failure cases
- 3Use descriptive test names
- 4Test edge cases and boundary conditions
- 5Keep tests independent
- 6Use fixtures for setup
- 7Test gas consumption
- 8Mock external contracts
- 9Test access control
- 10Verify events emission
Conclusion
Hardhat provides everything needed for professional smart contract development. Master its testing framework, and you'll build more reliable, secure contracts with confidence.