如何用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'); });
解决方案:正确的合约交互模拟方式
错误原因分析
- 未提前初始化合约状态:mint函数执行前会校验
paused、maxMintAmount、maxSupply、cost、totalSupply等变量,若未提前mock这些只读方法的返回值,会导致校验失败或读取未定义属性。 - 参数匹配错误:Vue代码中调用
mint时传入了三个参数(_to、_mintAmount、overrides),之前的mock方式未正确匹配这些参数,导致模拟失败。 - 交易回调逻辑错误:未在交易完成后更新
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
相关产品推荐
相关产品推荐

