You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用eth-testing模拟智能合约交易测试Vue NFT Mint功能

问题:Vue应用NFT Mint功能的合约交互模拟失败

我需要测试Vue应用中的NFT Mint功能,用户调用该功能时应能铸造NFT,这需要调用智能合约的mint函数。合约的mint函数包含供应量、权限、金额等校验逻辑。合约初始总供应量为5,铸造1个NFT后总供应量应变为6。我使用eth-testing库模拟合约交互,编写Jest测试用例时,尝试多种mockTransaction、mockCall调用方式均出现错误(如参数无效、读取未定义属性等),无法正确模拟交易完成测试,现寻求正确的模拟方法。

Vue应用的mint函数代码

mint: async function(){
  if(typeof window.ethereum !== 'undefined') {
    let accounts = await window.ethereum.request({method : 'eth_requestAccounts'});
    const provider = new ethers.providers.Web3Provider(window.ethereum);
    const signer = provider.getSigner();
    const contract = new ethers.Contract(this.contractAddress, NftContract.abi, signer);
    try {
      let overrides = {
        from: accounts[0],
        value: this.data.cost
      }
      // 无法模拟的交易调用
      const transaction = await contract.mint(accounts[0], 1, overrides);
      await transaction.wait();
      this.getData();
      this.setSuccess('The NFT mint is successful');
    } catch(err) {
      console.log(err);
      this.setError('An error occured to mint');
    }
  }
}

智能合约的mint函数代码

function mint(address _to, uint256 _mintAmount) public payable {
  uint256 supply = totalSupply();
  require(!paused);
  require(_mintAmount > 0);
  require(_mintAmount <= maxMintAmount);
  require(supply + _mintAmount <= maxSupply);

  if (msg.sender != owner()) {
      if(whitelisted[msg.sender] != true) {
        require(msg.value >= cost * _mintAmount);
      }
  }

  for (uint256 i = 1; i <= _mintAmount; i++) {
    _safeMint(_to, supply + i);
  }
}

当前Jest测试代码

it('when the user mint 1 NFT, the totalSupply should increment and a successful message should appear (mint funtion)', async () => {
// Start with not connected wallet
testingUtils.mockNotConnectedWallet();
// Mock the connection request of MetaMask
const account = testingUtils.mockRequestAccounts(["0xe14d2f7105f759a100eab6559282083e0d5760ff"]);
//allows to mock the chain ID / network to which the provider is connected --> 0x3 Ropsten network
testingUtils.mockChainId("0x3");
// Mock the network to Ethereum main net
testingUtils.mockBlockNumber("0x3");

const abi = NftContract.abi;
// An address may be optionally given as second argument, advised in case of multiple similar contracts
const contractTestingUtils = testingUtils.generateContractUtils(abi);
let transaction;
//transaction = await contractTestingUtils.mockCall("mint", account, String('10000000000000000')); //Invalid argument
//transaction = await contractTestingUtils.mockCall("mint"); //bad result from back end
//transaction = await contractTestingUtils.mockCall("mint", [account, 1, ethers.utils.parseUnits("0.01", "ether")]); //Invalid argument
//transaction = await contractTestingUtils.mockTransaction("mint"); //Cannot read properties of undefined (reading 'toLowerCase')
transaction = await contractTestingUtils.mockTransaction("mint", undefined, {
    triggerCallback: () => {
      contractTestingUtils.mockCall("cost", ['10000000000000000']);
      contractTestingUtils.mockCall("totalSupply", ['5']);
    }
}); //Cannot read properties of undefined (reading 'toLowerCase')

await wrapper.vm.mint();
await wrapper.vm.getData();

console.log('********wrapper.vm.data');
console.log(wrapper.vm.data);

expect(wrapper.vm.data.totalSupply).toBe('6');
});

解决方案:正确的合约交互模拟方式

错误原因分析

  1. 未提前初始化合约状态:mint函数执行前会校验paused、maxMintAmount、maxSupply、cost、totalSupply等变量,若未提前mock这些只读方法的返回值,会导致校验失败或读取未定义属性。
  2. 参数匹配错误:Vue代码中调用mint时传入了三个参数(_to、_mintAmount、overrides),之前的mock方式未正确匹配这些参数,导致模拟失败。
  3. 交易回调逻辑错误:未在交易完成后更新totalSupply的mock值,无法验证供应量递增的结果。

修复后的测试代码

it('用户铸造1个NFT后,总供应量应增加且显示成功消息', async () => {
  // 模拟未连接钱包状态
  testingUtils.mockNotConnectedWallet();
  // 模拟MetaMask账户请求,返回测试账户
  const testAccount = "0xe14d2f7105f759a100eab6559282083e0d5760ff";
  testingUtils.mockRequestAccounts([testAccount]);
  // 模拟链ID(Ropsten)
  testingUtils.mockChainId("0x3");
  // 模拟区块号
  testingUtils.mockBlockNumber("0x3");

  const abi = NftContract.abi;
  // 传入合约地址,避免多合约混淆
  const contractTestingUtils = testingUtils.generateContractUtils(abi, wrapper.vm.contractAddress);

  // 提前mock合约只读方法,满足mint的前置校验条件
  contractTestingUtils.mockCall("paused", [], [false]); // 合约未暂停
  contractTestingUtils.mockCall("maxMintAmount", [], [1]); // 单次最大铸造量为1
  contractTestingUtils.mockCall("maxSupply", [], [100]); // 总供应量上限足够
  contractTestingUtils.mockCall("cost", [], [ethers.utils.parseUnits("0.01", "ether")]); // 单个NFT成本
  contractTestingUtils.mockCall("totalSupply", [], [5]); // 初始总供应量为5
  // 若测试非白名单/非owner账户,需mock whitelisted和owner方法
  contractTestingUtils.mockCall("owner", [], ["0x其他地址"]); // 当前账户不是owner
  contractTestingUtils.mockCall("whitelisted", [testAccount], [false]); // 当前账户不在白名单

  // 模拟mint交易,匹配调用参数
  contractTestingUtils.mockTransaction(
    "mint",
    [testAccount, 1, { value: ethers.utils.parseUnits("0.01", "ether") }],
    {
      triggerCallback: () => {
        // 交易完成后,更新totalSupply的mock值为6
        contractTestingUtils.mockCall("totalSupply", [], [6]);
      }
    }
  );

  // 调用组件的mint方法
  await wrapper.vm.mint();
  // 等待getData获取更新后的状态
  await wrapper.vm.getData();

  // 断言总供应量更新为6
  expect(wrapper.vm.data.totalSupply).toBe('6');
  // 断言成功消息已设置
  expect(wrapper.vm.successMessage).toBe('The NFT mint is successful');
});

关键注意点

  • 前置状态初始化:必须在调用mint前mock所有合约校验依赖的只读方法,确保mint函数的require条件全部满足。
  • 参数精确匹配:Vue中调用mint的参数包括目标地址、铸造数量和overrides对象,mockTransaction时需严格匹配这些参数;若无需校验参数,可传入undefined作为参数匹配器。
  • 交易后状态更新:在交易的triggerCallback中更新totalSupply的mock值,保证getData能获取到铸造后的最新供应量。
  • 合约地址指定:生成合约工具时传入实际合约地址,避免多个合约实例的mock冲突。

内容的提问来源于stack exchange,提问作者DamTan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 12:50:28