As suggested in the comments I recommend Simmy.
It allows you to inject exceptions, return BadRequests
and etc. in order to trigger Polly's fault and resilience policies such as WaitAndRetry
.
These are a few samples from the documentation.
Inject (Socket) exception
var chaosPolicy = MonkeyPolicy.InjectException(Action<InjectOutcomeOptions<Exception>>);
For example: it causes the policy to throw SocketException
with a probability of 5% if enabled
var fault = new SocketException(errorCode: 10013);
var chaosPolicy = MonkeyPolicy.InjectException(with =>
with.Fault(fault)
.InjectionRate(0.05)
.Enabled()
);
Inject (BadRequest) result
var chaosPolicy = MonkeyPolicy.InjectResult(Action<InjectOutcomeOptions<TResult>>);
For example: it causes the policy to return a bad request HttpResponseMessage with a probability of 5% if enabled
var result = new HttpResponseMessage(HttpStatusCode.BadRequest);
var chaosPolicy = MonkeyPolicy.InjectResult<HttpResponseMessage>(with =>
with.Result(result)
.InjectionRate(0.05)
.Enabled()
);
Simply set the InjectionRate
to 1
to guarantee a fault in your unit test.
If you want to use the InjectionRate
less than 1 you can use xunit and moq chaining via SetupSequence
and Moq.Language.ISetupSequentialResult
. Here's an example from an blockchain challenge I had to do, I execute 4 calls in a row, so if the InjectionRate is 0.25 one of the 4 calls would trigger a Polly policy:
[Fact]
public async Task Should_Return_GetEthereumTransactionsAsync()
{
// Arrange
IConfiguration settings = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
IOptions<Settings> optionSettings = Options.Create(new Settings
{
CompanyKeyAPI = settings.GetSection("CompanyKeyAPI").Value,
ProjectId = settings.GetSection("ProjectId").Value
});
var sequenceHttpResponse = new List<Tuple<HttpStatusCode, HttpContent>>
{
new Tuple<HttpStatusCode, HttpContent>(HttpStatusCode.OK, ApiCompanyKeyResponses.EthereumBlockWithTransactionHashs()),
new Tuple<HttpStatusCode, HttpContent>(HttpStatusCode.OK, ApiCompanyKeyResponses.Transaction(1)),
new Tuple<HttpStatusCode, HttpContent>(HttpStatusCode.OK, ApiCompanyKeyResponses.Transaction(2)),
new Tuple<HttpStatusCode, HttpContent>(HttpStatusCode.OK, ApiCompanyKeyResponses.Transaction(3))
};
IHttpClientFactory httpClientFactory = base.GetChainedCompanyKeyHttpClientFactory(new Uri(Path.Combine(optionSettings.Value.CompanyKeyAPI, optionSettings.Value.ProjectId)), sequenceHttpResponse);
CompanyKeyService CompanyKeyService = new CompanyKeyService(httpClientFactory);
// Act
List<EthereumTransaction> ethereumTransactionsResult = CompanyKeyService.GetEthereumTransactionsAsync(blockNumber, address).Result;
// Assert
Assert.IsType<List<EthereumTransaction>>(ethereumTransactionsResult);
Assert.NotNull(ethereumTransactionsResult);
Assert.Equal(ethereumTransactionsResult.Count, 3);
Assert.Equal(ethereumTransactionsResult[0].result.blockHash, blockHash);
}
public IHttpClientFactory GetChainedCompanyKeyHttpClientFactory(Uri uri, List<Tuple<HttpStatusCode, HttpContent>> httpReturns, HttpStatusCode statusCode = HttpStatusCode.OK)
{
Mock<HttpMessageHandler> httpMsgHandler = new Mock<HttpMessageHandler>();
var handlerPart = httpMsgHandler.Protected().SetupSequence<Task<HttpResponseMessage>>("SendAsync", new object[2]
{
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
});
foreach (var httpResult in httpReturns)
{
handlerPart = AddReturnPart(handlerPart, httpResult.Item1, httpResult.Item2);
}
httpMsgHandler.Verify();
HttpClient client = new HttpClient(httpMsgHandler.Object)
{
BaseAddress = uri
};
Mock<IHttpClientFactory> clientFactory = new Mock<IHttpClientFactory>();
clientFactory.Setup((IHttpClientFactory cf) => cf.CreateClient(It.IsAny<string>())).Returns(client);
return clientFactory.Object;
}
private Moq.Language.ISetupSequentialResult<Task<HttpResponseMessage>> AddReturnPart(Moq.Language.ISetupSequentialResult<Task<HttpResponseMessage>> handlerPart,
HttpStatusCode statusCode, HttpContent content)
{
return handlerPart
// prepare the expected response of the mocked http call
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = statusCode,
Content = content
});
}
....
public class CompanyKeyService : ICompanyKeyService
{
private readonly IHttpClientFactory _clientFactory;
private readonly HttpClient _client;
public CompanyKeyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
_client = _clientFactory.CreateClient("GitHub");
}
public async Task<List<EthereumTransaction>> GetEthereumTransactionsAsync(string blockNumber, string address)
{
//Validation removed...
List<string> transactionHashs = await GetEthereumTransactionHashsByBlockNumberAsync(blockNumber);
if (transactionHashs == null) throw new Exception("Invalid entry. Please check the Block Number.");
var tasks = transactionHashs.Select(hash => GetTransactionByHashAsync(hash, address));
EthereumTransaction[] lists = await Task.WhenAll(tasks);
return lists.Where(item => item != null).ToList();
}
}