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

如何验证Moq方法调用时引用类型参数的真实取值?

问题:验证方法调用时的引用类型参数状态,避免测试误过

先看背景代码:
正确的业务代码:

public class ExampleClass
{
  public void ExampleMethod(IMyInterface myInterface)
  {
    var test = new DataClass();
    test.Id = 5;
    myInterface.DoSomething(test);
  }
}

public class DataClass
{
  public int Id {get;set;}
}

用来验证的测试代码:

[TestMethod]
public void Test()
{
  var mock = new Mock<IMyInterface>();
  var example = new ExampleClass();
  example.ExampleMethod(mock.Object);
  mock.Verify(x => x.DoSomething(It.Is<DataClass>(y => y.Id == 5)));
}

当业务代码出现bug——先调用DoSomething再给Id赋值:

public void ExampleMethod(IMyInterface myInterface)
{
  var test = new DataClass();
  myInterface.DoSomething(test);
  test.Id = 5;
}

此时测试本该失败,但因为DataClass是引用类型,Verify执行时Id已经被改成5,导致测试误通过。需要让测试能正确识别这个bug。


解决办法

1. 用Callback捕获调用瞬间的参数状态

在Mock的方法上注册Callback,在DoSomething被调用的瞬间就把当时的Id值存下来,后续直接断言这个保存的值,不受后续修改影响。

[TestMethod]
public void Test()
{
    int capturedId = -1;
    var mock = new Mock<IMyInterface>();
    
    // 注册回调,捕获调用时的Id
    mock.Setup(x => x.DoSomething(It.IsAny<DataClass>()))
        .Callback<DataClass>(data => capturedId = data.Id);

    var example = new ExampleClass();
    example.ExampleMethod(mock.Object);

    // 先确认方法确实被调用过,再断言捕获到的Id
    mock.Verify(x => x.DoSomething(It.IsAny<DataClass>()), Times.Once);
    Assert.AreEqual(5, capturedId);
}

这样在bug场景下,capturedId会是0(默认值),断言失败,测试就能正确识别问题。

2. 把DataClass改成值类型(业务允许的话)

值类型在传递时是复制副本,DoSomething拿到的是调用时的独立副本,后续修改原始实例的Id不会影响这个副本。

// 将DataClass改为struct值类型
public struct DataClass
{
    public int Id { get; set; }
}

此时原测试代码就能正确失败,因为调用DoSomething时传入的副本Id是0,后续修改的是另一个实例的Id,Verify检查的是调用时的副本状态。

3. 使用不可变类型(推荐方案)

让DataClass变成不可变,构造时就初始化Id,后续无法修改,从根源上避免后续修改参数状态的问题。

public class DataClass
{
    public int Id { get; } // 只读属性,只能在构造时赋值
    public DataClass(int id)
    {
        Id = id;
    }
}

// 修正后的业务代码写法
public void ExampleMethod(IMyInterface myInterface)
{
    var test = new DataClass(5); // 构造时直接初始化Id
    myInterface.DoSomething(test);
}

这种情况下,bug代码(先调用方法再赋值)会直接编译不通过,因为Id是只读的,必须在构造时设置。如果有其他类似的状态修改操作,也无法改变实例状态,测试时Verify检查的就是调用时的真实状态。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 22:20:28