关于 hardhat-toolbox-mocha-ethers
为核心 `hardhat` 技能补充使用 ethers、Mocha 和 chai 编写智能合约测试的指南,面向 Hardhat 项目开发者。
主要功能
- 01配置 Mocha 测试与网络连接
- 02使用 ethers 签名者和合约交互
- 03通过 fixture 部署合约
- 04模拟地址并为账户注资
- 05断言交易、事件与余额变化
适用场景
- 01测试合约读取和状态变更调用
- 02在测试间复用部署设置
- 03检查回滚行为和触发的事件
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ nomicfoundation/ hardhat-skills --skill hardhat-toolbox-mocha-ethers安装后在 Claude Code 中输入/hardhat-toolbox-mocha-ethers确认已加载。
详细介绍
Hardhat 工具箱:ethers + Mocha
此技能基于核心 hardhat 技能。请先加载该技能,了解测试组织方式、network.create() 的结构、networkHelpers、fixture 和类型检查工作流。以下内容都基于 network.create() 返回的连接。
此技术栈使用 Mocha(全局 describe / it)运行测试,并使用 chai 进行断言。Mocha 不会等待 describe 回调,因此要在文件顶部使用顶层 await 来建立连接:
import { expect } from "chai";
import { network } from "hardhat";
const { ethers, networkHelpers } = await network.create();
describe("Counter", function () {
// ...
});
通常每个文件建立一个连接。测试之间的状态隔离通过 loadFixture(参见 hardhat 技能)实现,而不是在每个 describe 中创建新的连接。
ethers:签名者和合约交互
合约类型由 TypeChain 根据编译后的 ABI 生成。请在进行类型检查前运行 hardhat build,确保类型反映最新的合约。
// Provider (read-only chain data)
const blockNumber = await ethers.provider.getBlockNumber();
const balance = await ethers.provider.getBalance("0xabc...");
// Signers (for sending transactions)
const [owner, alice, bob] = await ethers.getSigners(); // default funded accounts
// Utilities
const oneEth = ethers.parseEther("1.0");
// Deploy a contract, returns a fully typed instance.
// Pass constructor args as the second argument, and (optionally) a signer
// other than the default as the third.
const counter = await ethers.deployContract("Counter");
const counterWithArgs = await ethers.deployContract("Counter", [42n]);
const counterFromAlice = await ethers.deployContract("Counter", [], alice);
// Read state and call functions. Args and return types are type-checked
// against the ABI at compile time; passing wrong types or wrong argument
// counts is a TypeScript error.
const value = await counter.x();
await counter.inc();
await counter.incBy(3n);
// Send from a different signer
await counter.connect(alice).inc();
// Send ETH along with a payable call
await counter.deposit({ value: oneEth });
// Attach to an already-deployed contract
const existing = await ethers.getContractAt("Counter", "0xabc...");
在 loadFixture 设置函数内部(周边模式请参见 hardhat 技能),ethers.deployContract 是标准的部署步骤:
async function deployCounterFixture() {
const counter = await ethers.deployContract("Counter");
return { counter };
}
const { counter } = await networkHelpers.loadFixture(deployCounterFixture);
若要从任意地址(而非默认获得资金的账户)发送交易,请为其提供 gas 费,并使用 ethers.getImpersonatedSigner 获取一个模拟签名者——它会在内部处理模拟,无需单独调用 networkHelpers.impersonateAccount:
const addr = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
await networkHelpers.setBalance(addr, ethers.parseEther("1.0"));
const signer = await ethers.getImpersonatedSigner(addr);
await counter.connect(signer).inc();
Chai 匹配器:针对以太坊的断言
hardhat-ethers-chai-matchers 插件为 chai 的 expect 扩展了用于交易、事件和余额变化的匹配器。将交易 Promise(写入调用的结果,不要使用 await)传入 expect:
import { expect } from "chai";
// Reverts
await expect(counter.connect(banned).inc()).to.revert(ethers);
await expect(counter.connect(banned).inc()).to.be.revertedWith(
"only the owner can increment the counter",
);
await expect(counter.connect(banned).inc()).to.be.revertedWithCustomError(
counter,
"Unauthorized",
);
await expect(counter.connect(banned).inc())
.to.be.revertedWithCustomError(counter, "Unauthorized")
.withArgs(banned.address);
// Events
await expect(counter.inc()).to.emit(counter, "Increment");
await expect(counter.inc()).to.emit(counter, "Increment").withArgs(1n);
// ETH balance changes (positive = received, negative = spent, before gas)
await expect(game.claim()).to.changeEtherBalance(ethers, winner, PRIZE);
await expect(game.claim()).to.changeEtherBalances(
ethers,
[winner, loser],
[PRIZE, -STAKE],
);
对于普通断言(相等性、数组、类型),直接使用 chai 的 expect,无需以太坊匹配器。