如何在Foundry中模拟返回不同结果的外部调用?
在Foundry中模拟同一外部调用返回不同结果
Foundry完全支持模拟同一外部调用多次返回不同结果的场景,下面提供几种实用的实现方式:
方法1:使用带调用次数限制的vm.mockCall
vm.mockCall有重载版本支持指定count参数,限制该mock仅生效指定次数。通过两次调用vm.mockCall分别设置不同返回值和生效次数,就能实现两次调用返回不同结果:
function testCalculateDelta() public { address calculatorAddr = address(0x123); // 目标合约地址 TargetContract target = new TargetContract(); // 第一次调用`getValue`返回10,仅生效1次 vm.mockCall( calculatorAddr, 1, // 生效次数 abi.encodeWithSelector(ICalculator.getValue.selector), abi.encode(int256(10)) ); // 第二次调用`getValue`返回20,仅生效1次 vm.mockCall( calculatorAddr, 1, abi.encodeWithSelector(ICalculator.getValue.selector), abi.encode(int256(20)) ); // 执行测试并验证结果 int256 delta = target.getDelta(calculatorAddr); assertEq(delta, 10); }
方法2:使用vm.expectCall指定每次调用的返回值
vm.expectCall可以预期外部调用的发生,并绑定对应返回值。连续调用两次vm.expectCall,分别匹配两次getValue的调用场景:
function testCalculateDeltaWithExpectCall() public { address calculatorAddr = address(0x123); TargetContract target = new TargetContract(); // 预期第一次`getValue`调用,返回10 vm.expectCall( calculatorAddr, abi.encodeWithSelector(ICalculator.getValue.selector) ).returns(abi.encode(int256(10))); // 预期`update`调用(可选,用于验证该方法确实被执行) vm.expectCall( calculatorAddr, abi.encodeWithSelector(ICalculator.update.selector) ); // 预期第二次`getValue`调用,返回20 vm.expectCall( calculatorAddr, abi.encodeWithSelector(ICalculator.getValue.selector) ).returns(abi.encode(int256(20))); // 执行测试 int256 delta = target.getDelta(calculatorAddr); assertEq(delta, 10); }
方法3:自定义模拟合约
如果需要更复杂的状态逻辑,可编写自定义模拟合约,通过内部状态控制不同调用的返回值:
// 模拟ICalculator接口的合约 contract MockCalculator is ICalculator { uint256 private _callCount; int256 public preUpdateValue; int256 public postUpdateValue; constructor(int256 pre, int256 post) { preUpdateValue = pre; postUpdateValue = post; } function getValue() external returns (int256) { _callCount++; return _callCount == 1 ? preUpdateValue : postUpdateValue; } function update() external {} // 空实现,仅满足接口要求 } // 测试函数 function testCalculateDeltaWithMockContract() public { MockCalculator mockCalc = new MockCalculator(10, 20); TargetContract target = new TargetContract(); int256 delta = target.getDelta(address(mockCalc)); assertEq(delta, 10); }
各方法适用场景
- 方法1适合简单的固定次数不同返回值场景,代码简洁;
- 方法2适合需要验证外部调用顺序、次数的场景;
- 方法3适合需要模拟复杂状态变化或逻辑的场景,扩展性更强。
内容的提问来源于stack exchange,提问作者Costin Razvan
相关产品推荐
相关产品推荐

