使用ethers/Hardhat测试Chainlink预言机已完成请求的最佳实践
解决方案
官方测试用例逻辑澄清
你之前看到的官方测试属于单元测试场景,和你当前的集成测试场景完全不同:该测试是直接模拟预言机节点身份主动调用fulfillOracleRequest方法发响应交易,不需要等待链下Chainlink节点的异步处理流程,所以直接等自己发起的这笔交易确认即可,自然不需要额外监听事件。而你需要等真实链下节点收到请求、处理、再上链发响应,属于异步等待场景,逻辑不一致是正常的。
最优实现方案:监听合约事件(无需修改合约)
你不需要额外编写存储映射和读取方法,直接利用Consumer合约本身已经触发的RequestFulfilled事件,配合ethers的事件监听API即可实现等待逻辑:
// 1. 发送请求,解析得到目标requestId const requestTx = await consumerContract.sendRequest(/* 你的请求入参 */); const requestReceipt = await requestTx.wait(); // 解析请求日志拿到requestId,也可以用Chainlink官方工具库解码 const requestEvent = consumerContract.interface.parseLog(requestReceipt.logs[0]); const targetRequestId = requestEvent.args.requestId; // 2. 封装Promise监听对应requestId的响应事件 const waitForFulfill = (requestId) => { return new Promise((resolve) => { consumerContract.once("RequestFulfilled", (fulfilledId, result, event) => { if (fulfilledId === requestId) { consumerContract.removeAllListeners("RequestFulfilled"); resolve({ result, event }); } }); }); }; // 3. 等待响应完成,直接拿结果做断言 const { result } = await waitForFulfill(targetRequestId); console.log("请求响应结果:", result.toString());
公测试网兼容优化(可选)
如果在Rinkeby这类公测试网测试,担心网络波动导致事件监听漏单,可以搭配区块过滤逻辑兜底:
const waitForFulfillTestnet = async (requestId, startBlock) => { while(true) { const currentBlock = await ethers.provider.getBlockNumber(); const fulfillEvents = await consumerContract.queryFilter("RequestFulfilled", startBlock, currentBlock); const matched = fulfillEvents.find(item => item.args.requestId === requestId); if (matched) return { result: matched.args.result, event: matched }; // 间隔2秒重试,避免RPC限流 await new Promise(resolve => setTimeout(resolve, 2000)); } }; // 调用时传入请求发送时的区块号即可 const { result } = await waitForFulfillTestnet(targetRequestId, requestReceipt.blockNumber);
方案优势
- 完全不需要修改合约代码,无需额外开发请求ID映射、查询接口
- 比轮询合约状态的方案节省RPC调用次数,事件触发即可立刻拿到结果,延迟更低
- 兼容本地Hardhat节点、Rinkeby测试网等所有测试场景
内容的提问来源于stack exchange,提问作者Lucas Moskun
相关产品推荐
相关产品推荐

