当参数与给定模式不匹配时,如何使 NSubstitute 模拟失败?
How do I get NSubstitute mocks to fail when the arguments don't match the given pattern?
我负责测试我的团队正在维护的用 C 和 C# 开发的遗留软件。最初的团队使用 NSubstitute 3.1 为委托创建测试替身,以便对 C# 部分的 API 执行单元测试。这是一个这样的测试替身,其中省略了不相关的细节:
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;
}
现在,如果使用与 fake()
调用中指定的参数相匹配的参数调用假委托,它会 returns retVal
值,每个人都会很高兴。但是,如果某些值不匹配,它 returns 为零。由于零是一个有效但不正确的值,因此执行继续并且我得到一个错误,该错误不是我正在测试的问题的根本原因(即错误的输出,而问题实际上是错误的输入)
我正在寻找一种方法:
- 为不符合预期的值指定“捕获所有”行为,或者
- 如果参数与预期不匹配则抛出异常
以便测试用例在接收到带有有意义消息的错误输入时立即失败,而不会触发只会污染测试结果的进一步行为。
提前致谢,
德克
P.S。如果确实有必要,我可能可以安全地切换到更新版本的 NSubstitute。
specify a "catch all" behaviour for the values that won't match the expectations
我想我已经找到了一种方法可以做到这一点。如果您首先存根所有参数的“全部捕获”/失败案例,则可以存根更具体的调用。 NSubstitute 将尝试匹配提供的最新规范,回退到早期的存根值。
这是一个示例。
请注意,它正在使用 NSubstitute 4.x 中引入的 NSubstitute.Extensions
命名空间中的 Configure
。这不是绝对必要的,因为如果您使用参数匹配器,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"));
}
您可以扩展它以包括有关不匹配参数的信息,但这将是一些自定义工作。如果您查看 NSubstitute 的一些参数格式化代码,这些代码可能可以重复使用以帮助解决这个问题。
更新以包含委托示例
我只是 运行 这与一个委托相反,它也通过了:
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"));
}
我负责测试我的团队正在维护的用 C 和 C# 开发的遗留软件。最初的团队使用 NSubstitute 3.1 为委托创建测试替身,以便对 C# 部分的 API 执行单元测试。这是一个这样的测试替身,其中省略了不相关的细节:
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;
}
现在,如果使用与 fake()
调用中指定的参数相匹配的参数调用假委托,它会 returns retVal
值,每个人都会很高兴。但是,如果某些值不匹配,它 returns 为零。由于零是一个有效但不正确的值,因此执行继续并且我得到一个错误,该错误不是我正在测试的问题的根本原因(即错误的输出,而问题实际上是错误的输入)
我正在寻找一种方法:
- 为不符合预期的值指定“捕获所有”行为,或者
- 如果参数与预期不匹配则抛出异常
以便测试用例在接收到带有有意义消息的错误输入时立即失败,而不会触发只会污染测试结果的进一步行为。
提前致谢,
德克
P.S。如果确实有必要,我可能可以安全地切换到更新版本的 NSubstitute。
specify a "catch all" behaviour for the values that won't match the expectations
我想我已经找到了一种方法可以做到这一点。如果您首先存根所有参数的“全部捕获”/失败案例,则可以存根更具体的调用。 NSubstitute 将尝试匹配提供的最新规范,回退到早期的存根值。
这是一个示例。
请注意,它正在使用 NSubstitute 4.x 中引入的 NSubstitute.Extensions
命名空间中的 Configure
。这不是绝对必要的,因为如果您使用参数匹配器,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"));
}
您可以扩展它以包括有关不匹配参数的信息,但这将是一些自定义工作。如果您查看 NSubstitute 的一些参数格式化代码,这些代码可能可以重复使用以帮助解决这个问题。
更新以包含委托示例
我只是 运行 这与一个委托相反,它也通过了:
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"));
}