【问题标题】:Nsubstitute always returns null when argument is created inside method that is being tested在正在测试的方法中创建参数时,Nsubstitute 总是返回 null
【发布时间】:2020-03-28 03:54:38
【问题描述】:

假设我们有一个带有注入工作单元的服务

public class Service: IService
{ 
    IUnitOfWork unitOfWork;

   public Service(IUnitOfWork unitOfWork)
   {
       this.unitOfWork = unitOfWork;
   }

   public Item getResult(int id)
   {
       var parameter = new Parameter(id);        
      return _unitOfWork.Repository.GetItem(parameter);        

   }
}

还有这样的服务

public class Service: IService
{ 
    IUnitOfWork unitOfWork;

   public Service(IUnitOfWork unitOfWork)
   {
       this.unitOfWork = unitOfWork;
   }

   public Item getResult(Parameter parameter)
   {       
      return _unitOfWork.Repository.GetItem(parameter);        
   }
}

我想通过使用 NUnit 和 NSubstitute 的测试来测试这两个服务,如下所示:

      public void Test()
      {
        Item item= new Item();
        item.Id = 22;

        Param param = new param(item.Id);
        IRepository repository = Substitute.For<IRepository>();
        repository.GetItem(param).Returns(item);

        IUnitOfWork unitOfWork = Substitute.For<UnitOfWork>(repository);

        IService sut= new Service(unitOfWork);


       // var result = service.GetResult(id) //Example for first service, returns null
        var result = service.GetResult(param)//Example for second service, returns item

        Assert.That(result.Id == item.Id);
      }

我想知道为什么第一个服务在测试时返回 null 而第二个服务返回正确的结果?

【问题讨论】:

  • 顺便说一句,我认为测试应该有Substitute.For&lt;IUnitOfWork&gt;(使用接口而不是UnitOfWork 类),或者可能使用真正的UnitOfWork 类。对于 NSubstitute,我们需要是 very careful using classes。将NSubstitute.Analyzers 添加到您的测试项目将有助于找出可能导致问题的案例。
  • @David Tchepak 是的,我非常同意,最安全的方法是使用界面,也就是说这只是一个突出不同之处的示例

标签: c# unit-testing .net-core nunit nsubstitute


【解决方案1】:

第二个有效,因为用于设置测试的实例与执行测试时使用的完全相同,而第一个失败,因为被测方法使用了自己的实例。

您在此处传递了对param 的引用

repository.GetItem(param).Returns(item); 

在测试设置中失败的主题服务使用它自己的对象。

public Item getResult(int id) {
    var parameter = new Parameter(id);        
    return _unitOfWork.Repository.GetItem(parameter);
}

您基本上是在告诉模拟“当repository.GetItem 被赋予该特定对象时,返回item”但在主题中它没有这样做,因此模拟不会按预期运行。

如果您希望测试适用于这两种情况,则需要放松参数匹配。

您可以使用 Arg.Any&lt;T&gt; 忽略传递的参数

public void Test() {
    var id = 22
    Item item = new Item();
    item.Id = id;

    var parameter = new Parameter(item.Id);

    IUnitOfWork unitOfWork = Substitute.For<IUnitOfWork>();
    //simplified to recursive mock
    unitOfWork.Repositoty.GetItem(Arg.Any<Parameter>()).Returns(item);

    IService service1 = new Service1(unitOfWork);
    IService service2 = new Service2(unitOfWork);

    //Act
    var result1 = service1.GetResult(id);
    var result2 = service2.GetResult(parameter);    

    //Assert
    Assert.That(result1.Id == item.Id);
    Assert.That(result2.Id == item.Id);
}

或者使用Arg.Is&lt;T&gt;(Predicate&lt;T&gt; condition)有条件地匹配参数。

假设Parameter 具有通过构造函数设置的object Value 属性。

//...

var parameter = new Parameter(item.Id);

IUnitOfWork unitOfWork = Substitute.For<IUnitOfWork>();
//simplified to recursive mock
unitOfWork.Repositoty.GetItem(Arg.Is<Parameter>(p => p.Value == parameter.Value))
    .Returns(item);

//...

参考NSubstitute: Argument matchers

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-09-02
    • 2019-05-27
    • 2018-09-24
    • 2011-06-15
    • 2020-08-16
    • 2021-12-31
    • 2012-05-24
    • 2015-07-10
    相关资源
    最近更新 更多