When your System Under Test (SUT) runs asynchronously, it needs time to reach the correct state. During this time your Then
steps should not fail, execution of it needs to:
- Complete when it runs successfully. This avoids unnecessary delays.
- Retry when it throws during the allowable time.
- Stop and throw last exception when the maximum time is reached.
Register a IRetryStepPluginConfiguration
in the scenario container. This object is responsible to decide which step-definitions are eligible for retry. Additionally, register a IAsyncRetryer
. This object is responsible for doing the retry logic.
For example:
[BeforeScenario]
public static void BeforeScenario(IObjectContainer c)
{
c.RegisterInstanceAs<IRetryStepPluginConfiguration>(new RetryThen());
c.RegisterInstanceAs<IAsyncRetryer>(new PollyRetryer());
}
//This test project wants all `Then` steps to be retried.
public class RetryThen : IRetryStepPluginConfiguration
{
public bool RetryEnabledFor(BindingMatch match)
=> match.StepBinding.StepDefinitionType == StepDefinitionType.Then;
}
[Then("this step definition will be retried in case of transient failures")]
public void DoCheck() => MySystem.State.Should().BeOK();
The plugin registers a custom ITestExecutionEngine
. This custom engine derives from the default Reqnroll TestExecutionEngine
but overrides the ExecuteStepMatchAsync
method to:
- Retrieve the
IRetryStepPluginConfiguration
from the ScenarioContainer and ask if the currentBindingMatch
should be retried. - If retry is needed, it retrieves an
IAsyncRetryer
from the ScenarioContainer to perform the retry logic on the invocation of the binding like this
await Retryer.RetryAsync(async () => await _bindingInvoker.InvokeBindingAsync(match.StepBinding, _contextManager, arguments, _testTracer, durationHolder));