Reputation: 6693
I'm trying to design a unit test to test a retry loop pattern. The only way I can think to do this is to change what's returned by the method embedded at the heart of the retry loop half way through the test.
For example... I'd like to throw an exception for a particular method for the first 5 seconds of the test. And then stop that exception from being thrown and actually respond with some valid data after that point.
For the first 5 seconds:
service.MethodToRetry(Arg.Any<string>()).ThrowsForAnyArgs(new Exception());
And then after that the exception condition is removed and MethodToRetry() completes normally.
Is this possible or am I going about it completely the wrong way? I'm working in c# with xunit and nsubstitute.
Upvotes: 1
Views: 2315
Reputation: 10484
Note: the tests here are to demonstrate NSubstitute's behaviour. In real tests we wouldn't test a substitute. :)
One way to test retries is to stub multiple returns (this may not work in your case if you need a condition rather than failing for a specific number of calls, but thought I'd start off with the simplest approach):
[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(""));
}
Another option is to put the condition in while you are stubbing the call:
[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(""));
}
As a modified version of this approach, you can also replace the callback used for the stub:
[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(""));
}
Ideally we'd try to avoid timing-dependent logic in tests, but if it is really necessary we could update the condition in the second example after 5 seconds to get the behaviour mentioned in your question.
Upvotes: 4
Reputation: 36
Fist of all, I don't see any concrete implementations so I'll put it in general.
Thoughts:
Upvotes: 0