First I need to put in the usual disclaimer about over-specifying tests. If you need this much real behaviour from a substitute you may be testing too much or you may be better using a real implementation (or a hand-rolled test double that behaves exactly how you want).
That said, you can get more control over what is returned by passing a function that manages it for you:
public interface IFoo { string Stuff(); }
[Test]
public void Example()
{
var foo = Substitute.For<IFoo>();
var returns = ReturnsQueue<string>
.FirstRepeat("hello", 2)
.ThenRepeat("world", 3)
.Then("!");
foo.Stuff().Returns(x => returns.Next());
for (int i = 0; i < 6; i++)
{
Console.WriteLine(foo.Stuff());
}
/* outputs:
hello
hello
world
world
world
!
*/
}
public class ReturnsQueue<T>
{
public static ReturnsQueue<T> FirstRepeat(T value, int times) { return new ReturnsQueue<T>().ThenRepeat(value, times); }
public static ReturnsQueue<T> First(T value) { return new ReturnsQueue<T>().Then(value); }
private readonly Queue<Func<T>> _returns = new Queue<Func<T>>();
private ReturnsQueue() { }
public ReturnsQueue<T> Then(T value) { _returns.Enqueue(() => value); return this; }
public ReturnsQueue<T> ThenRepeat(T value, int times)
{
foreach (var v in Enumerable.Repeat(value, times)) { Then(v); }
return this;
}
public T Next() { return _returns.Dequeue().Invoke(); }
}