NSubstitute 使用 return 值对被调用方法进行有序测试 (Recived.InOrder),导致 CouldNotSetReturnDueToMissingInfoAboutLastCallException

NSubstitute ordered testing (Recived.InOrder) with return values for called methods results in CouldNotSetReturnDueToMissingInfoAboutLastCallException

我以前用 RhinoMocks 编写测试,现在改用 NSubstitute。 现在我有一个关于有序测试的问题。 可以说我有三个小 类 像

public interface IProvider
{
    int GetData();
}

public class Provider : IProvider
{
    public int GetData()
    {
        return 3;
    }
}

public interface ICalculator
{
    int Calculate(int data);
}

public class Calculator : ICalculator
{
    public int Calculate(int data)
    {
        if (data < 3)
        {
            return data;
        }

        return data * 2;
    }
}

public class Operator
{
    public void Operate(IProvider provider, ICalculator calculator)
    {
        int version = provider.GetData();

        this.Result = calculator.Calculate(version);
    }

    public int Result
    {
        get;
        private set;
    }
}

当我使用 RhinoMocks 编写有序测试时,我可以像这样定义模拟 类 的行为:

[Test]
    public void RhinoMockOrderedTest()
    {
        var mockRepository = new MockRepository();
        var provider = mockRepository.DynamicMock<IProvider>();
        var calculator = mockRepository.DynamicMock<ICalculator>();

        using (mockRepository.Ordered())
        {
            provider.Expect(p => p.GetData()).Return(4);
            calculator.Expect(c => c.Calculate(4)).Return(9);
        }

        mockRepository.ReplayAll();

        var op = new Operator();
        op.Operate(provider, calculator);

        mockRepository.VerifyAll();

        Assert.That(op.Result, Is.EqualTo(9));
    }

现在我尝试使用 NSubstitute 编写一个像上面那样的有序测试,我还尝试检查调用顺序并使用定义的 return 值:

[Test]
    public void NSubstituteOrderedTest()
    {
        var provider = Substitute.For<IProvider>();
        var calculator = Substitute.For<ICalculator>();

        var op = new Operator();
        op.Operate(provider, calculator);

        Received.InOrder(() =>
        {
            provider.GetData().Returns(4);
            calculator.Calculate(4).Returns(9);
        });

        Assert.That(op.Result, Is.EqualTo(9));
    }

不幸的是,这不起作用。在我看来,当我尝试将 .Returns 用于 Received.InOrder - 操作中的 methon 时,它总是会像这样失败:

NSubstitute.Exceptions.CouldNotSetReturnDueToMissingInfoAboutLastCallException : Could not find information about the last call to return from.

Make sure you called Returns() after calling your substitute (for example: mySub.SomeMethod().Returns(value)), and that you are not configuring other substitutes within Returns() (for example, avoid this: mySub.SomeMethod().Returns(ConfigOtherSub())).

If you substituted for a class rather than an interface, check that the call to your substitute was on a virtual/abstract member. Return values cannot be configured for non-virtual/non-abstract members.

Correct use: mySub.SomeMethod().Returns(returnValue);

Potentially problematic use: mySub.SomeMethod().Returns(ConfigOtherSub()); Instead try: var returnValue = ConfigOtherSub(); mySub.SomeMethod().Returns(returnValue);

如何使用 NSubstitute 编写此测试?

谢谢,

妮可

NSubstitute 在此处与 Rhino Mocks 的工作方式不同——它仅支持 Arrange-Act-Assert (AAA) 样式测试。这意味着我们需要去除我们感兴趣的调用 (arrange),运行 我们要测试的代码 (act),然后断言结果符合预期 (assert)。

Received.InOrder is only for assertions, and works like NSubstitute's Received() method for each call. Returns 安排调用 return 特定结果。 NSubstitute 不允许我们将两者混合。我们不能做 sub.Received().Calculate().Returns(42),这在 AAA 中没有意义,因为在断言我们已经对被测试的主题采取行动并收到所有必需的调用后,存根 return 值毫无意义.

这是将 stubbing/arranging 与断言分开的问题的测试通过版本:

[Test]
public void NSubstituteOrderedTest() {
    // Arrange
    var provider = Substitute.For<IProvider>();
    var calculator = Substitute.For<ICalculator>();

    provider.GetData().Returns(4);
    calculator.Calculate(4).Returns(9);

    // Act
    var op = new Operator();
    op.Operate(provider, calculator);

    // Assert
    Received.InOrder(() =>
    {
        provider.GetData();
        calculator.Calculate(4);
    });
    Assert.That(op.Result, Is.EqualTo(9));
}

旁白:我知道这是一个简化的示例,但我认为值得注意的是,在许多情况下,我们可以在不测试调用顺序的情况下逃脱。对于这个简单的例子,我们知道 GetData() 首先被调用,因为它的值被传递给 Calculate(),所以这个顺序是通过数据依赖来强制执行的。如果最终结果是正确的,我们就知道调用链是正确的。对于更复杂的情况,我们可以为此使用类型(Connect() returns ConnectedDb,然后 Query(ConnectedDb db) 确保 Connect() 首先被调用。

依赖于对被测试代码的实现细节的了解(例如调用顺序)可能会导致脆弱的测试(即它们会因不应该影响整体结果的小改动而失败),所以最好尽可能避免这种情况。

但是,即使有此免责声明,有时断言调用顺序还是很有用的,所以我希望这个答案能为您解决这个 NSubstitute 功能。 :)