Как изменить возвращаемое значение метода во время выполнения модульного теста
Я пытаюсь разработать модульный тест для проверки шаблона повторения цикла. Единственный способ, которым я могу думать, - это изменить то, что возвращает метод, встроенный в сердце цикла повторения, в середине теста.
Например... Я хотел бы выдать исключение для конкретного метода в течение первых 5 секунд теста. И затем остановите это исключение от выдачи и фактически ответьте с некоторыми действительными данными после этой точки.
За первые 5 секунд:
service.MethodToRetry(Arg.Any<string>()).ThrowsForAnyArgs(new Exception());
А затем после этого исключение условие удаляется и MethodToRetry() завершается нормально.
Возможно ли это или я поступаю совершенно неправильно? Я работаю в C# с Xunit и Nsubstitute.
2 ответа
Примечание: тесты здесь должны продемонстрировать поведение NSubstitute. В реальных тестах мы не будем тестировать замену. :)
Один из способов протестировать повторные попытки - заблокировать несколько возвратов (это может не сработать в вашем случае, если вам нужно условие, а не сбой для определенного числа вызовов, но я решил начать с самого простого подхода):
[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(""));
}
В идеале мы стараемся избегать логики, зависящей от времени, в тестах, но если это действительно необходимо, мы можем обновить условие во втором примере через 5 секунд, чтобы получить поведение, упомянутое в вашем вопросе.
Во-первых, я не вижу конкретных реализаций, поэтому я изложу это в общем.
Мысли:
- Поскольку вы хотите протестировать "шаблон повторения цикла", я предполагаю, что у вас есть логика части "5 секунд ожидания". Эта логика должна быть инъекционным вызовом, поэтому в своем тесте вы можете проверить, был ли он вызван или нет. ( http://nsubstitute.github.io/help/received-calls/)
- Часть ожидания не должна быть в ваших тестах, потому что ваш метод должен ждать в повторных шагах, а не тестах.