【问题标题】:How to mock protected method with NSubstitute如何使用 NSubstitute 模拟受保护的方法
【发布时间】:2016-05-29 20:34:09
【问题描述】:
public static void Mock(out IProgram prog, out IJson json)
{    
    prog = Substitute.For<IProgram>();
    IJson = Substitute.For<IJson>();

    prog.SaveProg(1, 1, 1, "Somthing", 1, DateTime.UtcNow,
                 (DateTime.UtcNow + TimeSpan.FromDays(10)), 
                 10, "someemail@email.com", DateTime.UtcNow, 1)
        .Returns(ObjectResult<int?>); 
}

调用Returns(ObjectResult&lt;int?&gt;) 时出现错误,因为ObjectResult 是受保护的类。我该如何解决这个问题才能从实际方法中调用我的模拟方法?

【问题讨论】:

  • 感谢 Arturo 编辑
  • 虚构示例的问题在于,如果您不检查它们,它们比没有意义更糟糕。您可能希望解决您的问题的许多问题。您给出的代码示例无法编译。 .Returns 需要一个实例,而不是一个类型。您当前正在模拟接口,这些接口不能具有受保护的方法。 ObjectResult&lt;T&gt; 不是受保护的类(需要嵌套类才能受到保护)。 ObjectResult 有不同的版本,其中一些是密封的,其中一些具有受保护的构造函数。您尝试使用哪个版本?
  • 你为什么关心你看不到的类的结果?您能否以一种根据 ObjectResult 的结果起作用的方式进行测试,这样您就不必接触类?

标签: c# unit-testing object mocking nsubstitute


【解决方案1】:

您不应该模拟受保护的类/方法。它受到明确保护,因此您不能这样做。如果您需要模拟它,请将其公开。如果它是别人的方法,而您认为需要模拟它,那么您可能测试不正确。

编辑:受保护方法中的任何功能只能由同一类中的公共方法使用。模拟该公共方法以根据受保护方法给出一些所需的结果来执行任何操作。

【讨论】:

  • 我无法更改访问修饰符。除了我已经知道的,你还有什么建议?
  • 方法不太可能被标记为受保护以明确防止模拟/防止创建测试替身。在某些情况下,例如 ObjectResult&lt;T&gt; 受保护的构造函数是专门添加的,以方便创建测试替身。 msdn.microsoft.com/en-us/library/…`1.
  • 如果您不应该模拟受保护的方法,那么为什么要使用privateprotected?这确实是测试框架的一个限制,因为我可以扩展类并模拟出受保护的方法。如果它真的打算改变它应该有virtual,但我仍然可以嘲笑它。做所有这些来创建一个测试替身会导致非常繁重的代码,我希望模拟框架可以减少这些代码。
  • 我们可以从您的测试项目中的类继承并将方法/属性导出为公共的,可以在内部获取/设置/访问受保护的成员?
【解决方案2】:

NSubstitute 会在您调用替代方法后覆盖该方法的行为,但它实际上并不关心您如何调用该方法。这允许您通过反射调用它。

下面是一个非常详细的例子:

public class SomeRepository
{
    public string ReadData() => ActuallyPerformDataReading();
    protected virtual string ActuallyPerformDataReading() => "some wrong data";
}

public class SomeClass
{
    SomeRepository _someRepository;
    public SomeClass(SomeRepository someRepository)
    {
        _someRepository = someRepository;
    }

    public string ReadSomething() => _someRepository.ReadData();
}


var repositorySub = Substitute.For<SomeRepository>();
repositorySub.GetType().GetMethod("ActuallyPerformDataReading", BindingFlags.NonPublic | BindingFlags.Instance)
    .Invoke(repositorySub, new object[] {}).Returns("some test data");
var sut = new SomeClass(repositorySub);

var result = sut.ReadSomething(); //"some test data"

【讨论】:

  • 感谢这个简单的解决方案。最好能够以一种可以被认为是骇人听闻的方式测试代码,而不是根本不测试(当您无法更改相关代码时)!
猜你喜欢
  • 2016-03-09
  • 1970-01-01
  • 2012-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-17
  • 2018-10-12
相关资源
最近更新 更多