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

基于对象属性而非引用设置Mock返回类型的NSubstitute单元测试问题

解决NSubstitute中Mock内部new对象的匹配问题

我太懂这个痛点了!当被测试方法里自己new了一个对象传给依赖的构建器时,NSubstitute默认的引用匹配确实会让我们的Mock配置直接失效——毕竟方法内部new出来的对象和我们在测试代码里创建的对象引用完全不一样,NSubstitute认不出来。

问题核心

NSubstitute默认对方法参数采用引用相等匹配:只有当传入的参数和你配置Mock时用的参数是同一个对象(引用完全一致),才会触发你设置的返回值。但如果被测试方法内部是new Request { ... }这种方式创建对象,测试代码里的对象和它根本不是同一个引用,自然匹配不上。

解决方案:用参数匹配器实现属性匹配

NSubstitute提供了灵活的参数匹配器,我们可以用Arg.Is<T>(predicate)来指定“只要参数满足某个属性条件,就触发Mock的返回”,完全绕开引用匹配的限制。

举个实际的代码例子:

假设被测试的业务代码是这样的:

public class OrderProcessor
{
    private readonly IOrderBuilder _orderBuilder;

    public OrderProcessor(IOrderBuilder orderBuilder)
    {
        _orderBuilder = orderBuilder;
    }

    public OrderResult CreateOrder(int customerId, decimal amount)
    {
        // 内部new了一个OrderRequest对象
        var orderRequest = new OrderRequest 
        { 
            CustomerId = customerId, 
            Amount = amount 
        };
        return _orderBuilder.BuildOrder(orderRequest);
    }
}

public interface IOrderBuilder
{
    OrderResult BuildOrder(OrderRequest request);
}

public class OrderRequest 
{ 
    public int CustomerId { get; set; } 
    public decimal Amount { get; set; } 
}
public class OrderResult { public bool IsSuccess { get; set; } }

错误的Mock写法(不会生效)

如果你直接这么写,测试会失败,因为引用不匹配:

var mockBuilder = Substitute.For<IOrderBuilder>();
// 这里的OrderRequest和方法内部new的不是同一个引用
mockBuilder.BuildOrder(new OrderRequest { CustomerId = 123, Amount = 99.9m })
           .Returns(new OrderResult { IsSuccess = true });

var processor = new OrderProcessor(mockBuilder);
var result = processor.CreateOrder(123, 99.9m); // result会是null,匹配失败

正确的Mock写法(用Arg.Is匹配属性)

改用Arg.Is指定属性条件,就能完美匹配内部new的对象:

var mockBuilder = Substitute.For<IOrderBuilder>();
// 只要传入的OrderRequest的CustomerId是123且Amount是99.9m,就返回成功结果
mockBuilder.BuildOrder(Arg.Is<OrderRequest>(req => 
    req.CustomerId == 123 && req.Amount == 99.9m))
           .Returns(new OrderResult { IsSuccess = true });

var processor = new OrderProcessor(mockBuilder);
var result = processor.CreateOrder(123, 99.9m); // 现在能正确拿到IsSuccess=true的结果了

进阶用法

如果不需要精确匹配所有属性,还可以:

  • 用Arg.Any<OrderRequest>()匹配任何类型为OrderRequest的参数:
    mockBuilder.BuildOrder(Arg.Any<OrderRequest>())
               .Returns(new OrderResult { IsSuccess = true });
    
  • 结合多个条件灵活匹配,比如忽略某个属性:
    // 只要CustomerId是123,不管Amount是多少都返回成功
    mockBuilder.BuildOrder(Arg.Is<OrderRequest>(req => req.CustomerId == 123))
               .Returns(new OrderResult { IsSuccess = true });
    

补充:不推荐重写Equals

虽然重写OrderRequest的Equals和GetHashCode方法也能让NSubstitute按值匹配,但这种方式会侵入业务代码,可能对其他逻辑造成意外影响,所以更推荐用参数匹配器的方式,更灵活且安全。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 07:36:51