资讯动态

Hardhat 3 模板实战:基于 node:test 原生测试运行器与 viem 的 TypeScript 测试与部署

发布时间:2026/9/16 11:58:26 来源:尧图企业网站定制
Hardhat 3 模板实战基于 node:test 原生测试运行器与 viem 的 TypeScript 测试与部署【免费下载链接】hardhatHardhat is a development environment to compile, deploy, test, and debug your Ethereum software.项目地址: https://gitcode.com/GitHub_Trending/ha/hardhat本篇指南以 Hardhat 官方模板01-node-test-runner-viem位于 packages/hardhat/templates/01-node-test-runner-viem为蓝本深入解析 Hardhat 3 中原生 Node 测试运行器 viem 库的现代开发工作流。读完本文你将掌握如何编写 Foundry 兼容的 Solidity 单元测试、使用node:test与 viem 编写类型安全的 TypeScript 集成测试、通过 Ignition 将合约部署到本地模拟链或 Sepolia 测试网以及如何在本地模拟 OP 主网发送交易。模板项目概览该模板是一个典型的 Hardhat 3 项目采用node:testNode.js 原生测试运行器作为测试框架使用viem进行以太坊交互。与 Hardhat 2 时代常见的 Mocha ethers 组合不同这条技术路线零额外测试框架依赖、完全类型安全是 Hardhat 3 推荐的入门起点。模板项目的目录结构如下01-node-test-runner-viem/ ├── contracts/ │ ├── Counter.sol # 示例合约 │ └── Counter.t.sol # Foundry 兼容的 Solidity 单元测试 ├── ignition/ │ └── modules/ │ └── Counter.ts # Ignition 部署模块 ├── scripts/ │ └── send-op-tx.ts # 在本地模拟 OP 主网发送交易 ├── test/ │ └── Counter.ts # node:test viem 集成测试 ├── gitignore # 初始化为 .gitignore 的模板文件 ├── hardhat.config.ts # Hardhat 3 配置文件 ├── package.json └── tsconfig.json从 模板目录说明 可以了解到该目录作为hardhat --init的初始化模板存在其中的package.json声明了模板元数据description用于模板选择时的展示devDependencies与peerDependencies用于安装依赖workspace:前缀在初始化时会被剥离其余文件会原样复制到新项目中。gitignore文件之所以不叫.gitignore是因为 npm 打包时总会忽略.gitignore文件模板初始化时会将其改名为.gitignore复制到项目。依赖清单与版本基线从 package.json 可以看出模板的技术栈基线运行时type: module整个项目以 ESM 方式组织代码核心依赖hardhat、nomicfoundation/hardhat-toolbox-viemviem 全家桶入口插件、nomicfoundation/hardhat-ignition部署框架测试与链交互viem、forge-stdFoundry 标准库以 git 依赖形式引入foundry-rs/forge-std#v1.16.2工具链typescript、types/nodepeerDependenciesnomicfoundation/hardhat-ignition-viem、nomicfoundation/hardhat-keystore、nomicfoundation/hardhat-network-helpers、nomicfoundation/hardhat-node-test-runner、nomicfoundation/hardhat-viem、nomicfoundation/hardhat-viem-assertions、nomicfoundation/hardhat-verify、nomicfoundation/ignition-core。其中hardhat-node-test-runner是让npx hardhat test驱动node:test的关键插件hardhat-keystore用于管理配置变量下文 Sepolia 部署会用到。对应的 tsconfig.json 采用es2023/node20编译目标并开启verbatimModuleSyntax与 ESM 项目保持严格一致。深入 hardhat.config.ts网络、编译器与配置变量模板的 hardhat.config.ts 是整个项目的配置核心展示了 Hardhat 3 的几个新特性import hardhatToolboxViemPlugin from nomicfoundation/hardhat-toolbox-viem; import { configVariable, defineConfig } from hardhat/config; export default defineConfig({ plugins: [hardhatToolboxViemPlugin], solidity: { profiles: { default: { version: 0.8.34, }, production: { version: 0.8.34, settings: { optimizer: { enabled: true, runs: 200, }, }, }, }, }, networks: { hardhatMainnet: { type: edr-simulated, chainType: l1, }, hardhatOp: { type: edr-simulated, chainType: op, }, sepolia: { type: http, chainType: l1, url: configVariable(SEPOLIA_RPC_URL), accounts: [configVariable(SEPOLIA_PRIVATE_KEY)], }, }, });defineConfig 与插件体系defineConfig由 hardhat/config 导出。从 config.ts 源码 可见它的作用是为 JS/TS 用户提供配置的自动补全并允许在单条语句中定义并导出配置其返回值就是传入的配置对象本身。与 Hardhat 2 在hardhat.config上挂载扩展不同Hardhat 3 改为显式的plugins数组声明插件——这里只引入了hardhat-toolbox-viem一个插件它聚合了 viem 客户端、断言库、网络助手、Ignition 集成、验证等能力。solidity profiles多套编译配置Hardhat 3 用profiles取代了旧版的单一编译设置defaultprofile 使用 Solidity 0.8.34不启用优化器利于快速测试与调试productionprofile 在相同版本上开启优化器runs: 200。你可以在命令行通过--profile参数切换编译配置例如发布前用npx hardhat compile --profile production产出优化过的字节码。三种网络类型的对比网络名typechainType用途hardhatMainnetedr-simulatedl1本地模拟以太坊 L1 主网无需 RPChardhatOpedr-simulatedop本地模拟 OP Stack L2支持 L1 gas 估算sepoliahttpl1连接真实 Sepolia 测试网 RPCedr-simulated是 Hardhat 3 内置的本地模拟网络类型由 EDR 引擎驱动chainType决定模拟的链语义l1对应普通以太坊op则启用 OP Stack 的 gas 与交易模型。这一设计在 packages/hardhat/test/internal/builtin-plugins/network-manager/edr/edr-provider.ts 的测试配置中也有印证——该测试以type: edr-simulated、chainType: op、chainId: 10构造本地 Optimism 网络并支持forking参数从真实主网拉取状态。configVariable敏感信息不落盘configVariable(SEPOLIA_RPC_URL)与configVariable(SEPOLIA_PRIVATE_KEY)是 Hardhat 3 的配置变量机制值不写死在配置文件中而是从环境变量或hardhat-keystore插件管理的密钥库中解析。这保证了私钥、RPC URL 等敏感信息与源码分离安全部署到真实网络。示例合约与 Foundry 兼容的 Solidity 测试Counter 合约contracts/Counter.sol 是一个带状态与事件的计数合约// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.34; contract Counter { uint public x; event Increment(uint by); function inc() public { x; emit Increment(1); } function incBy(uint by) public { require(by 0, incBy: increment should be positive); x by; emit Increment(by); } }它包含两个核心交互点无参的inc()自增 1 并发出Increment(1)事件和带参的incBy(uint by)带require校验by必须大于 0为后续单元测试与集成测试提供了充分的断言场景。Foundry 风格的 Solidity 单元测试contracts/Counter.t.sol 演示了 Hardhat 3 对 Foundry 测试生态的兼容合约继承forge-std的Test基类函数以test_/testFuzz_前缀命名并使用vmcheatcodes// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.34; import {Counter} from ./Counter.sol; import {Test} from forge-std/Test.sol; contract CounterTest is Test { Counter counter; function setUp() public { counter new Counter(); } function test_InitialValue() public view { require(counter.x() 0, Initial value should be 0); } function testFuzz_Inc(uint8 x) public { for (uint8 i 0; i x; i) { counter.inc(); } require(counter.x() x, Value after calling inc x times should be x); } function test_IncByZero() public { vm.expectRevert(); counter.incBy(0); } }三个测试分别覆盖三类典型场景test_InitialValue普通状态断言验证初始值x 0testFuzz_Inc(uint8 x)模糊测试由框架自动生成x的随机取值循环调用inc()后验证x与调用次数一致test_IncByZero预期回滚借助vm.expectRevert()验证incBy(0)会因require(by 0)而回滚。forge-std依赖在 package.json 中以foundry-rs/forge-std#v1.16.2的 git 引用声明Hardhat 会负责拉取并解析import {Test} from forge-std/Test.sol这样的导入路径。用 node:test 与 viem 编写类型安全的集成测试test/Counter.ts 是模板最核心的演示文件它把 Node.js 原生测试运行器和 viem 组合成一个无需额外测试框架的集成测试import assert from node:assert/strict; import { describe, it } from node:test; import { network } from hardhat; describe(Counter, async function () { const { viem } await network.create(); const publicClient await viem.getPublicClient(); it(Should emit the Increment event when calling the inc() function, async function () { const counter await viem.deployContract(Counter); await viem.assertions.emitWithArgs( counter.write.inc(), counter, Increment, [1n], ); }); it(The sum of the Increment events should match the current value, async function () { const counter await viem.deployContract(Counter); const deploymentBlockNumber await publicClient.getBlockNumber(); // run a series of increments for (let i 1n; i 10n; i) { await counter.write.incBy([i]); } const events await publicClient.getContractEvents({ address: counter.address, abi: counter.abi, eventName: Increment, fromBlock: deploymentBlockNumber, strict: true, }); // check that the aggregated events match the current value let total 0n; for (const event of events) { total event.args.by; } assert.equal(total, await counter.read.x()); }); });测试骨架node:test 的 describe/it测试直接使用node:test导出的describe/it组织用例配合node:assert/strict做断言完全不需要安装 Mocha、Chai 等第三方测试框架。npx hardhat test通过hardhat-node-test-runner插件将测试文件交给 Node 原生运行器执行。network.create()测试上下文入口const { viem } await network.create()是 Hardhat 3 测试中的标准模式。根据 hardhat-toolbox-viem 技能文档 的说明network.create()返回一个绑定到当前测试网络的连接对象其中viem暴露了完整的客户端与合约操作 APIviem.getPublicClient()只读公共客户端用于查询区块、获取合约事件viem.getWalletClients()钱包客户端数组对应默认测试账户viem.getTestClient()开发专用客户端提供挖矿等测试操作viem.deployContract(Counter)按合约名部署并返回完全类型化的合约实例viem.getContractAt(Counter, 0x...)附加到已部署合约。deployContract返回的实例是类型安全的counter.write.incBy([i])的参数个数与类型会对照 ABI 在编译期校验counter.read.x()的返回值类型也由 ABI 自动推断。技能文档特别提醒应避免使用walletClient.writeContract直接交互合约因为它没有 ABI 类型约束错误参数会被静默放过。事件断言viem.assertions.emitWithArgs第一个用例演示了viem.assertions断言库的用法——把未 await 的交易 Promise作为第一个参数传入await viem.assertions.emitWithArgs( counter.write.inc(), counter, Increment, [1n], );emitWithArgs会执行交易并断言其发出名为Increment的事件且参数为[1n]。断言库还提供revert、revertWith、revertWithCustomError、balancesHaveChanged等以太坊专用断言并支持在任意参数位置使用(value) boolean谓词或anyValue占位符参见 SKILL.md 中的完整断言清单。事件聚合校验读取链上数据第二个用例展示了端到端的数据一致性验证部署合约后记录部署区块号连续调用 10 次incBy再用publicClient.getContractEvents从部署区块开始拉取全部Increment事件strict: true表示严格按 ABI 解码累加所有args.by后与counter.read.x()的状态值比对。这是一个事件日志与合约状态一致的经典账本测试模式覆盖了从部署、写交易、读状态到日志检索的完整链路。运行测试一条命令两种粒度模板 README 给出的测试命令体系# 运行全部测试Solidity 单元测试 node:test 集成测试 npx hardhat test # 只运行 Foundry 风格的 Solidity 测试 npx hardhat test solidity # 只运行 node:test 的 TypeScript 集成测试 npx hardhat test nodejsnpx hardhat test是统一入口solidity与nodejs两个子命令分别过滤两类测试便于在迭代阶段快速只跑自己关心的部分。用 Ignition 部署合约从本地模拟链到 Sepolia模板内置了一个 Ignition 部署模块 ignition/modules/Counter.tsimport { buildModule } from nomicfoundation/hardhat-ignition/modules; export default buildModule(CounterModule, (m) { const counter m.contract(Counter); m.call(counter, incBy, [5n]); return { counter }; });模块逻辑清晰m.contract(Counter)声明部署合约m.call(counter, incBy, [5n])在部署后立即调用其incBy(5)方法这正是前文require(by 0)校验的合法路径最后返回命名部署结果供其他模块或脚本引用。Ignition 采用声明式模块与可复现部署模型同样的模块可以无差别地跑在本地模拟链与真实测试网上。部署到本地模拟链不指定网络时默认在本地模拟链上执行部署npx hardhat ignition deploy ignition/modules/Counter.ts部署到 Sepolia部署到 Sepolia 需要两步准备。首先需要一个持有测试代币的账户有资金支付 gas 费其次把该账户的私钥通过名为SEPOLIA_PRIVATE_KEY的配置变量提供给 Hardhat。配置变量有两种设置方式使用 hardhat-keystore 插件推荐私钥经本地密钥库加密管理npx hardhat keystore set SEPOLIA_PRIVATE_KEY通过环境变量设置适合 CI 或临时场景将SEPOLIA_PRIVATE_KEY导出为环境变量即可Hardhat 会在解析configVariable时读取。完成配置后带上--network sepolia参数执行部署npx hardhat ignition deploy --network sepolia ignition/modules/Counter.tsHardhat 会从配置变量读取SEPOLIA_RPC_URL连接测试网、读取SEPOLIA_PRIVATE_KEY作为交易签名账户对应 hardhat.config.ts 中sepolia网络的url与accounts配置并执行CounterModule完成合约部署与incBy(5)调用。本地模拟 OP 主网估算 L1 Gas 并发送交易模板的 scripts/send-op-tx.ts 演示了如何针对 OP Stack L2 网络编程展示了chainType: op的独特价值import { network } from hardhat; const { viem } await network.create({ network: hardhatOp, chainType: op, }); console.log(Sending transaction using the OP chain type); const publicClient await viem.getPublicClient(); const [senderClient] await viem.getWalletClients(); console.log(Sending 1 wei from, senderClient.account.address, to itself); const l1Gas await publicClient.estimateL1Gas({ account: senderClient.account.address, to: senderClient.account.address, value: 1n, }); console.log(Estimated L1 gas:, l1Gas); console.log(Sending L2 transaction); const tx await senderClient.sendTransaction({ to: senderClient.account.address, value: 1n, }); await publicClient.waitForTransactionReceipt({ hash: tx }); console.log(Transaction sent successfully);脚本通过network.create({ network: hardhatOp, chainType: op })显式连接到前面配置的 OP 模拟网络。与 L1 的关键差异在于OP Stack 交易需要支付 L1 数据可用性费用因此脚本先用publicClient.estimateL1Gas估算这笔 1 wei 转账的 L1 gas 成本再通过sendTransaction发送 L2 交易最后用waitForTransactionReceipt等待交易上链。整个过程完全在本地模拟环境中完成无需真实的 OP 主网 RPC是开发 OP 应用前验证交易流程的便捷手段。模板工作机制小结作为hardhat --init的官方模板之一01-node-test-runner-viem在仓库中承担开箱即用的现代 Hardhat 3 项目这一角色。从 模板机制文档 与仓库源码可以归纳出它的几条设计要点依赖分层devDependencies声明直接编译依赖peerDependencies声明需要随包安装的插件依赖初始化时workspace:版本前缀会被剥离为可发布的真实版本文件复制约定除package.json外的所有文件在初始化时复制进新项目gitignore会改名为.gitignore规避 npm 打包限制一条测试链贯穿全程Solidity 单元测试Foundry 风格、TypeScript 集成测试node:test viem、Ignition 部署本地与 Sepolia共享同一套Counter合约与配置让读者可以在一个最小闭环中完整走通编写合约 → 测试 → 部署 → 链上交互的 Hardhat 3 开发流程。无论你是刚从 Hardhat 2 迁移还是第一次接触 Hardhat 3以这个模板为起点配合 hardhat.config.ts 中的网络与编译配置、test/Counter.ts 中的 viem 测试范式以及 ignition/modules/Counter.ts 中的声明式部署即可快速搭建起一套现代、类型安全、可复现的以太坊开发环境。【免费下载链接】hardhatHardhat is a development environment to compile, deploy, test, and debug your Ethereum software.项目地址: https://gitcode.com/GitHub_Trending/ha/hardhat创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价