Unit testing async method: How to explicitly assert that the internal task was cancelled
<p>I was recently writing an <em>async</em> method that calls a external long running <em>async</em> method so I decided to pass <em>CancellationToken</em> enabling cancellation. The method can be called concurrently. </p>

<p>Implementation has combined <em>exponential backoff</em> and <em>timeout</em> techniques described in <a href="https://stackoverflow.com/users/263693/stephen-cleary">Stephen Cleary</a>'s book <em>Concurrency in C# Cookbook</em> as follows;</p>

<pre><code>/// &lt;summary&gt;
/// Sets bar
/// &lt;/summary&gt;
/// &lt;param name="cancellationToken"&gt;The cancellation token that cancels the operation&lt;/param&gt;
/// &lt;returns&gt;A &lt;see cref="Task"/&gt; representing the task of setting bar value&lt;/returns&gt;
/// &lt;exception cref="OperationCanceledException"&gt;Is thrown when the task is cancelled via &lt;paramref name="cancellationToken"/&gt;&lt;/exception&gt;
/// &lt;exception cref="TimeoutException"&gt;Is thrown when unable to get bar value due to time out&lt;/exception&gt;
public async Task FooAsync(CancellationToken cancellationToken)
{
    TimeSpan delay = TimeSpan.FromMilliseconds(250);
    for (int i = 0; i &lt; RetryLimit; i++)
    {
        if (i != 0)
        {
            await Task.Delay(delay, cancellationToken);
            delay += delay; // Exponential backoff
        }

        await semaphoreSlim.WaitAsync(cancellationToken); // Critical section is introduced for long running operation to prevent race condition

        using (CancellationTokenSource cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
        {
            cancellationTokenSource.CancelAfter(TimeSpan.FromMilliseconds(Timeout));
            CancellationToken linkedCancellationToken = cancellationTokenSource.Token;

            try
            {
                cancellationToken.ThrowIfCancellationRequested();
                bar = await barService.GetBarAsync(barId, linkedCancellationToken).ConfigureAwait(false);

                break;
            }
            catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
            {
                if (i == RetryLimit - 1)
                {
                    throw new TimeoutException("Unable to get bar, operation timed out!");
                }

                // Otherwise, exception is ignored. Will give it another try
            }
            finally
            {
                semaphoreSlim.Release();
            }
        }
    }
}
</code></pre>

<p>I wonder if I should write a unit test that explicitly asserts that the internal task <code>barService.GetBarAsync()</code> is cancelled whenever <code>FooAsync()</code> is cancelled. If so how to implement it cleanly?</p>

<p>On top of that, should I ignore implementation details and just test what client/caller is concerned as described in method summary (bar is updated, cancel triggers <code>OperationCanceledException</code>, timeout triggers <code>TimeoutException</code>). </p>

<p>If not, should I get my feet wet and start implementing unit tests for following cases:</p>

<ol>
<li>Testing it is thread-safe (monitor acquired only by single thread at a time)</li>
<li>Testing the retry mechanism</li>
<li>Testing the server is not flooded</li>
<li>Testing maybe even a regular exception is propagated to caller</li>
</ol>