James Law
James Law

Reputation: 6693

How to change the return value of a method in a unit test mid execution

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

Answers (2)

David Tchepak
David Tchepak

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

Binjaaa
Binjaaa

Reputation: 36

Fist of all, I don't see any concrete implementations so I'll put it in general.

Thoughts:

  1. Because you want to test a "retry loop pattern" I assume you have the logic of the "5 second wait" part. This logic should be an injectable call, so in your test you can check whether it has been called or not. (http://nsubstitute.github.io/help/received-calls/ )
  2. The waiting part shouldn't be in your tests because your method have to wait in retry steps not the tests.

Upvotes: 0

Related Questions