Reputation: 433
I'm recently learning and using Polly to add resilience to my code, especially for the timeout and retry policies. However, I don't know how to unit test the code with polly. To be more specific, I don't know how to mock a method with Cancellation Token as its parameter. Below is the structure of my code
public class Caller
{
private IHttpManager httpManager;
private IAsyncPolicy<HttpResponseMessage> policyWrap;
public Caller(IHttpManager httpManager, IAsyncPolicy<HttpResponseMessage> policyWrap)
{
this.httpManager= httpManager;
this.policyWrap = policyWrap;
}
public async Task CallThirdParty()
{
HttpResponseMessage httpResponse = await policyWrap.ExecuteAsync(async ct => await httpManager.TryCallThirdParty(ct), CancellationToken.None);
}
}
public interface IHttpManager
{
Task<HttpResponseMessage> TryCallThirdParty(CancellationToken cancellationToken);
}
Below is the unit test I intend to run but don't know how.
[Test]
public void TestBehaviourUnderTimeoutPolicy()
{
// Set up the timeout policy such that the governed delegate will terminate after 1 sec if no response returned
AsyncTimeoutPolicy<HttpResponseMessage> timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(1, TimeoutStrategy.Optimistic);
// mock IHttpManager
var mockHttpManager= new Mock<IHttpManager>();
// THIS IS WHERE I'M HAVING TROUBLE WITH.
// I want to simulate the behaviour of the method such that
// it will throw an exception whenever the CancellationToken passed from the polly caller expires
// But how can I do that with mock?
mockHttpManager.Setup(m => m.TryCallThirdParty(It.IsAny<CancellationToken>()))).Returns(Task.Run(() => { Thread.Sleep(10000); return new HttpResponseMessage(); }));
Caller c = new Caller(mockHttpManager.Object, timeoutPolicy);
await c.CallThirdParty();
}
Upvotes: 12
Views: 20382
Reputation: 22714
In this case you can pass a NoOpPolicy
as the policyWrap
parameter
//Arrange
var managerMock = new Mock<IHttpManager>();
IAsyncPolicy<HttpResponseMessage> policy = Policy.NoOpAsync<HttpResponseMessage>();
var sut = new Caller(managerMock.Object, policy);
//Act
await sut.CallThirdParty();
//Assert
//...
In this case you can simple create a mock for IAsyncPolicy
and call the Verify
//Arrange
var managerMock = new Mock<IHttpManager>();
var policyMock = new Mock<IAsyncPolicy<HttpResponseMessage>>();
var sut = new Caller(managerMock.Object, policyMock.Object);
//Act
await sut.CallThirdParty();
//Asssert
policyMock.Verify(p => p.ExecuteAsync(It.IsAny<Func<CancellationToken, Task<HttpResponseMessage>>>(), It.IsAny<CancellationToken>()), Times.Once);
In this case you can use ThrowsAsync
calls
//Arrange
var managerMock = new Mock<IHttpManager>();
var policyMock = new Mock<IAsyncPolicy<HttpResponseMessage>>();
policyMock
.Setup(p => p.ExecuteAsync(It.IsAny<Func<CancellationToken, Task<HttpResponseMessage>>>(), It.IsAny<CancellationToken>()))
.ThrowsAsync(new TimeoutRejectedException());
var sut = new Caller(managerMock.Object, policyMock.Object);
//Act + Assert
await Assert.ThrowsAsync<TimeoutRejectedException>(async () => await sut.CallThirdParty());
In this case you can use the Record
structure
//Arrange
var managerMock = new Mock<IHttpManager>();
var policyMock = new Mock<IAsyncPolicy<HttpResponseMessage>>();
policyMock
.Setup(p => p.ExecuteAsync(It.IsAny<Func<CancellationToken, Task<HttpResponseMessage>>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new HttpResponseMessage());
var sut = new Caller(managerMock.Object, policyMock.Object);
//Act
var exception = await Record.ExceptionAsync(async () => await sut.CallThirdParty());
//Asssert
Assert.Null(exception);
Upvotes: 1
Reputation: 2165
@Noremac is right (you test 2 elements in one unit test).
However, to answer the question "How to make Mock<T>
throw an exception when CancellationToken
is cancelled":
Use Returns<TParam1, ...>(Func<TResult, TParam1>)
overload. I replaced AsyncTimeoutPolicy
with CancellationTokenSource
for clarity.
// This test will perma-hang if the exception is not thrown
[TestMethod]
[ExpectedException(typeof(OperationCanceledException))]
public async Task TestMethod1()
{
var source = new Mock<IHttpManager>();
source.Setup(s => s.TryCallThirdParty(It.IsAny<CancellationToken>())).Returns<CancellationToken>(
async token =>
{
// Wait until the token is cancelled
await Task.Delay(Timeout.Infinite, token);
});
var tcs = new CancellationTokenSource(1000);
await new Caller().Get(source.Object, tcs.Token);
}
Upvotes: 13
Reputation: 95
your code snipped is never calling this method "c.TryCallThirdParty".
Did you tried to use Task.Delay(Timespan.FromSeconds(1))
instead of Thread.Sleep
? Its every time better to use Task.Delay instead of Thread.Sleep.
Upvotes: 0