如何在单元测试执行中更改方法的 return 值
How to change the return value of a method in a unit test mid execution
我正在尝试设计一个单元测试来测试重试循环模式。我能想到的唯一方法是在测试进行到一半时更改嵌入在重试循环核心的方法返回的内容。
例如...我想在测试的前 5 秒为特定方法抛出异常。然后停止抛出该异常,并在该点之后实际响应一些有效数据。
前 5 秒:
service.MethodToRetry(Arg.Any<string>()).ThrowsForAnyArgs(new Exception());
然后异常条件被移除,MethodToRetry() 正常完成。
这可能吗,还是我的做法完全错误?我正在使用 xunit 和 nsubstitute 在 c# 中工作。
首先,我没有看到任何具体的实现,所以我会笼统地说。
想法:
- 因为你想测试一个 "retry loop pattern" 我假设你有“5 秒等待”部分的逻辑。这个逻辑应该是一个可注入的调用,所以在你的测试中你可以检查它是否被调用过。
(http://nsubstitute.github.io/help/received-calls/ )
- 等待部分不应该在您的测试中,因为您的方法必须在重试步骤而不是测试中等待。
注意:这里的测试是为了演示 NSubstitute 的行为。在实际测试中,我们不会测试替代品。 :)
测试重试的一种方法是对多个 returns 进行存根(如果您需要一个条件而不是特定数量的调用失败,这在您的情况下可能不起作用,但我想我会从最简单的方法):
[Test]
public void StubMultipleCalls() {
Func<string> throwEx = () => { throw new Exception(); };
var sub = Substitute.For<IThingoe>();
// Stub method to fail twice, then return valid data
sub.MethodToRetry(Arg.Any<string>())
.Returns(x => throwEx(), x => throwEx(), x => "works now");
// The substitute will then act like this:
Assert.Throws<Exception>(() => sub.MethodToRetry(""));
Assert.Throws<Exception>(() => sub.MethodToRetry(""));
Assert.AreEqual("works now", sub.MethodToRetry(""));
// Will continue returning last stubbed value...
Assert.AreEqual("works now", sub.MethodToRetry(""));
Assert.AreEqual("works now", sub.MethodToRetry(""));
}
另一种选择是在调用时添加条件:
[Test]
public void StubWithCondition() {
var shouldThrow = true;
var sub = Substitute.For<IThingoe>();
sub.MethodToRetry(Arg.Any<string>()).Returns(x => {
if (shouldThrow) {
throw new Exception();
}
return "works now";
});
Assert.Throws<Exception>(() => sub.MethodToRetry(""));
shouldThrow = false; // <-- can alter behaviour by modifying this variable
Assert.AreEqual("works now", sub.MethodToRetry(""));
}
作为此方法的修改版本,您还可以替换用于存根的回调:
[Test]
public void ReplaceLambda() {
Func<string> methodToRetry = () => { throw new Exception(); };
var sub = Substitute.For<IThingoe>();
sub.MethodToRetry(Arg.Any<string>()).Returns(x => methodToRetry());
Assert.Throws<Exception>(() => sub.MethodToRetry(""));
methodToRetry = () => "works now";
Assert.AreEqual("works now", sub.MethodToRetry(""));
}
理想情况下,我们会尽量避免测试中的 timing-dependent 逻辑,但如果确实有必要,我们可以在 5 秒后更新第二个示例中的条件,以获得您问题中提到的行为。
我正在尝试设计一个单元测试来测试重试循环模式。我能想到的唯一方法是在测试进行到一半时更改嵌入在重试循环核心的方法返回的内容。
例如...我想在测试的前 5 秒为特定方法抛出异常。然后停止抛出该异常,并在该点之后实际响应一些有效数据。
前 5 秒:
service.MethodToRetry(Arg.Any<string>()).ThrowsForAnyArgs(new Exception());
然后异常条件被移除,MethodToRetry() 正常完成。
这可能吗,还是我的做法完全错误?我正在使用 xunit 和 nsubstitute 在 c# 中工作。
首先,我没有看到任何具体的实现,所以我会笼统地说。
想法:
- 因为你想测试一个 "retry loop pattern" 我假设你有“5 秒等待”部分的逻辑。这个逻辑应该是一个可注入的调用,所以在你的测试中你可以检查它是否被调用过。 (http://nsubstitute.github.io/help/received-calls/ )
- 等待部分不应该在您的测试中,因为您的方法必须在重试步骤而不是测试中等待。
注意:这里的测试是为了演示 NSubstitute 的行为。在实际测试中,我们不会测试替代品。 :)
测试重试的一种方法是对多个 returns 进行存根(如果您需要一个条件而不是特定数量的调用失败,这在您的情况下可能不起作用,但我想我会从最简单的方法):
[Test]
public void StubMultipleCalls() {
Func<string> throwEx = () => { throw new Exception(); };
var sub = Substitute.For<IThingoe>();
// Stub method to fail twice, then return valid data
sub.MethodToRetry(Arg.Any<string>())
.Returns(x => throwEx(), x => throwEx(), x => "works now");
// The substitute will then act like this:
Assert.Throws<Exception>(() => sub.MethodToRetry(""));
Assert.Throws<Exception>(() => sub.MethodToRetry(""));
Assert.AreEqual("works now", sub.MethodToRetry(""));
// Will continue returning last stubbed value...
Assert.AreEqual("works now", sub.MethodToRetry(""));
Assert.AreEqual("works now", sub.MethodToRetry(""));
}
另一种选择是在调用时添加条件:
[Test]
public void StubWithCondition() {
var shouldThrow = true;
var sub = Substitute.For<IThingoe>();
sub.MethodToRetry(Arg.Any<string>()).Returns(x => {
if (shouldThrow) {
throw new Exception();
}
return "works now";
});
Assert.Throws<Exception>(() => sub.MethodToRetry(""));
shouldThrow = false; // <-- can alter behaviour by modifying this variable
Assert.AreEqual("works now", sub.MethodToRetry(""));
}
作为此方法的修改版本,您还可以替换用于存根的回调:
[Test]
public void ReplaceLambda() {
Func<string> methodToRetry = () => { throw new Exception(); };
var sub = Substitute.For<IThingoe>();
sub.MethodToRetry(Arg.Any<string>()).Returns(x => methodToRetry());
Assert.Throws<Exception>(() => sub.MethodToRetry(""));
methodToRetry = () => "works now";
Assert.AreEqual("works now", sub.MethodToRetry(""));
}
理想情况下,我们会尽量避免测试中的 timing-dependent 逻辑,但如果确实有必要,我们可以在 5 秒后更新第二个示例中的条件,以获得您问题中提到的行为。