简体   繁体   English

当 arguments 与给定模式不匹配时,如何让 NSubstitute 模拟失败?

[英]How do I get NSubstitute mocks to fail when the arguments don't match the given pattern?

I'm responsible for the testing of a legacy software developed in C and C# that my team is maintaining.我负责测试我的团队正在维护的在 C 和 C# 中开发的遗留软件。 The original team used NSubstitute 3.1 to create test doubles for delegates in order to perform unit test of the APIs for the C# sections.最初的团队使用 NSubstitute 3.1 为代表创建测试替身,以便对 C# 部分的 API 执行单元测试。 Here's one such test double, where irrelevant details have been omitted:这是一个这样的测试替身,其中不相关的细节已被省略:

private static byte[] MockSelectByAidWithoutData(ushort retVal)
{
    var expectedIn= "FFFEFDFCFB".HexToBytes();
    var expectedOut= "010203040506070809".HexToBytes();

    var fake = Substitute.For<SomeDelegate>();
    fake(Arg.Is<byte[]>(x => expectedIn.SequenceEqual(x.Take(expectedIn.Length))),
            Arg.Is(0x00),
            Arg.Is(expectedIn.Length),
            Arg.Any<int>(),
            Arg.Any<int>(),
            out int outputLength)
        .Returns(x =>
            {
                expectedOut.CopyTo((Array)x[0], 0);
                x[5] = expectedOut.Length;
                return retVal;
            }
        );
    Mediator.GetInstance().Delegate = fake;
    return expectedOut;
}

Now, if the fake delegate is invoked with arguments that match what is specified in the fake() call, it returns the retVal value and everybody is happy.现在,如果使用与fake()调用中指定的匹配的 arguments 调用假委托,它会返回retVal值,每个人都很高兴。 However, if some value won't match, it returns zero.但是,如果某些值不匹配,则返回零。 Since zero is a valid but incorrect value, the execution continues and I get an error that is not the root cause of the issue I am testing (ie bad output when the problem is actually bad input)由于零是一个有效但不正确的值,执行继续,我得到一个错误,这不是我正在测试的问题的根本原因(即当问题实际上是错误的输入时,错误的 output)

I am looking for a way to either:我正在寻找一种方法:

  • specify a "catch all" behaviour for the values that won't match the expectations, or为不符合预期的值指定“全部捕获”行为,或
  • get an exception if the arguments don't match the expectation如果 arguments 不符合预期,则会出现异常

so that the test case would fail immediately upon reception of the wrong input with a meaningful message and without triggering further behaviour that would just pollute the outcome of the test.这样测试用例会在接收到带有有意义消息的错误输入时立即失败,并且不会触发只会污染测试结果的进一步行为。

Thanks in advance,提前致谢,

DeK德克

PS I can probably switch safely to a more recent version of NSubstitute if that's really necessary. PS 如果真的有必要,我可能可以安全地切换到更新版本的 NSubstitute。

specify a "catch all" behaviour for the values that won't match the expectations为不符合预期的值指定“全部捕获”行为

I think I've found a way you can do this.我想我找到了一种方法可以做到这一点。 If you first stub the "catch all" / failure case for all arguments, you can then stub more specific calls.如果您首先为所有 arguments 存根“全部捕获”/失败案例,然后您可以存根更具体的调用。 NSubstitute will try to match the most recent specifications provided, falling back to earlier stubbed values. NSubstitute 将尝试匹配提供的最新规范,回退到较早的存根值。

Here is a sample.这是一个示例。

Note it is using Configure from NSubstitute.Extensions namespace introduced in NSubstitute 4.x.请注意,它使用Configure 4.x 中引入的NSubstitute.Extensions命名空间中的配置。 This isn't strictly necessary because NSubstitute will automatically assume you are configuring a call if you are using argument matchers, but it is a good pattern to use when configuring overlapping calls like this.这不是绝对必要的,因为如果您使用参数匹配器,NSubstitute 会自动假定您正在配置调用,但是在配置这样的重叠调用时使用它是一个很好的模式。

using NSubstitute;
using NSubstitute.Extensions; // required for Configure()

public class Thing {
    public string Id { get; set; }
}

public interface ISample {
    int Example(Thing a, string b);
}

public class UnexpectedCallException : Exception { }

[Fact]
public void ExampleOfStubOneCallButFailOthers() {
    var sub = Substitute.For<ISample>();

    // Catch all case:
    sub.Example(null, null).ReturnsForAnyArgs(x => throw new UnexpectedCallException());

    // Specific case. We use Configure from NSubstitute.Extensions to
    // be able to stub this without getting an UnexpectedCallException.
    // Not strictly necessary here as we're using argument matchers so NSub
    // already knows we're configuring a call, but it's a good habit to get into.
    // See: https://nsubstitute.github.io/help/configure/
    sub.Configure()
        .Example(Arg.Is<Thing>(x => x.Id == "abc"), Arg.Any<string>())
        .Returns(x => 42);

    // Example of non-matching call:
    Assert.Throws<UnexpectedCallException>(() =>
        sub.Example(new Thing { Id = "def" }, "hi")
    );

    // Example of matching call:
    Assert.Equal(42, sub.Example(new Thing { Id = "abc" }, "hello"));
}

You could extend this to include information about arguments that do not match, but that will be a bit of custom work.您可以扩展它以包含有关不匹配的 arguments 的信息,但这将是一些自定义工作。 If you look at some of NSubstitute's argument formatting code that might be re-usable to help with this.如果您查看 NSubstitute 的一些参数格式化代码,这些代码可能可重用以帮助解决此问题。


Update to include delegate example更新以包含委托示例

I just ran this with a delegate instead and it also passes:我只是用一个委托来运行它,它也通过了:

public delegate int SomeDelegate(Thing a, string b);

[Fact]
public void ExampleOfStubOneDelegateCallButFailOthers() {
    var sub = Substitute.For<SomeDelegate>();
    sub(null, null).ReturnsForAnyArgs(x => throw new UnexpectedCallException());
    sub.Configure()
        .Invoke(Arg.Is<Thing>(x => x.Id == "abc"), Arg.Any<string>())
        .Returns(x => 42);
    Assert.Throws<UnexpectedCallException>(() => sub(new Thing { Id = "def" }, "hi"));
    Assert.Equal(42, sub(new Thing { Id = "abc" }, "hello"));
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM