Jane.L
Jane.L

Reputation: 433

How to mock a method with CancellationToken passed as parameter?

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

Answers (3)

Peter Csala
Peter Csala

Reputation: 22714

Testing the functionality like there were no policy/policies

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 
//...

Making sure that the policy has been used

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);

Making sure that exception is not swallowed

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());

Making sure that if result is received then there is no exception

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

orhtej2
orhtej2

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

Changer
Changer

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

Related Questions