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

如何用Jest模拟ethers.js中Contract.allowance方法?

解决Jest模拟ethers.js Contract的allowance方法失败的问题

问题原因分析

1. spyOn方法失败的原因

ethers.js的Contract实例方法是根据传入的ABI动态生成的,并非预先挂载在Contract.prototype上,所以直接用jest.spyOn(Contract.prototype, 'allowance')会找不到对应属性,报错是必然的。

2. 全局mock Contract类失败的原因

你写的mock代码存在几个问题:

  • 错误地将jest.mock的回调包裹在jest.fn().mockImplementation中,不符合Jest模块mock的语法;
  • 直接引用originalEthers会报错,因为jest.mock会被提升到文件最顶部,此时原模块还未完成导入;
  • 未模拟BigNumber,如果业务代码中用到了ethers.BigNumber,测试会因找不到该方法而失败。

正确的解决方案

方案一:直接模拟整个ethers模块

这种方式适合不需要用到ethers其他原生功能的场景,直接返回预设的模拟对象:

import approveService from './你的服务文件路径';

// 模拟ethers模块
jest.mock('ethers', () => {
  // 模拟BigNumber.from方法
  const mockBigNumber = {
    from: jest.fn(val => val)
  };

  // 模拟Contract类,实例化后包含allowance方法
  class MockContract {
    async allowance() {
      return mockBigNumber.from('100000');
    }
  }

  return {
    Contract: MockContract,
    BigNumber: mockBigNumber
  };
});

describe('getAllowance', () => {
  it('正常情况下应返回授权额度', async () => {
    expect.assertions(1);

    const response = await approveService.getAllowance(
      1116,
      'mock-token-address',
      'some wallet address',
    );

    expect(response.allowance).toBe('100000');
  });
});

方案二:保留ethers原生功能,仅动态模拟Contract实例

这种方式更灵活,适合需要用到ethers其他功能的场景,比如验证BigNumber的处理逻辑:

import { ethers } from 'ethers';
import approveService from './你的服务文件路径';

// 保留ethers原模块的大部分功能,仅替换Contract类为mock
jest.mock('ethers', () => {
  const originalEthers = jest.requireActual('ethers');
  return {
    ...originalEthers,
    Contract: jest.fn()
  };
});

describe('getAllowance', () => {
  it('正常情况下应返回授权额度', async () => {
    expect.assertions(2);

    // 模拟allowance方法的返回值
    const mockAllowance = jest.fn().mockResolvedValue(ethers.BigNumber.from('100000'));
    // 让Contract构造函数返回带有allowance方法的实例
    ethers.Contract.mockReturnValue({
      allowance: mockAllowance
    });

    const response = await approveService.getAllowance(
      1116,
      'mock-token-address',
      'some wallet address',
    );

    // 注意:如果返回的是BigNumber实例,需要转成字符串再断言
    expect(response.allowance.toString()).toBe('100000');
    // 可选:验证allowance方法是否被正确调用
    expect(mockAllowance).toHaveBeenCalledWith('some wallet address', expect.any(String));
  });
});

注意事项

  • 如果业务代码中最终返回的是BigNumber实例,断言时需要调用toString()转换为字符串,或者直接用BigNumber实例进行比较;
  • 测试用例中传入的参数(比如tokenAddress)要和业务代码的逻辑匹配,若代码不校验地址格式,用任意字符串即可;
  • 方案二中的jest.requireActual('ethers')会保留原模块的所有功能,仅替换Contract类,适合复杂场景下的测试。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 23:30:27