Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

}
like image 752
Jane.L Avatar asked Apr 02 '19 20:04

Jane.L


People also ask

What is cancellationtoken in a method?

A method has a CancellationToken parameter that is not the last parameter. By default, this rule analyzes the entire codebase, but this is configurable. Methods that perform long running operations or asynchronous operations and are cancelable normally take a cancellation token parameter.

When to use a cancellationtoken as the last parameter for method invocations?

If any of the method invocations can either accept a CancellationToken as the last parameter, or have an overload that takes a CancellationToken as the last parameter, then the rule suggests using that option instead to ensure that the cancellation notification gets propagated to all operations that can listen to it.

How do I make the cancellationtoken parameter optional?

If you want to accept CancellationToken but want to make it optional, you can do so with syntax such as this: It’s a good idea to only make your CancellationToken parameters optional in your public API (if you have one) and leave them as required parameters everywhere else.

How do I mock a class with one parameter?

The simplest overloaded variant of the mock method is the one with a single parameter for the class to be mocked: public static <T> T mock(Class<T> classToMock) We will use this method to mock a class and set an expectation: MyList listMock = mock(MyList.class); when(listMock.add(anyString())).thenReturn(false); Then execute a method on the mock:


1 Answers

@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);
}
like image 178
orhtej2 Avatar answered Oct 08 '22 09:10

orhtej2