Home > Software engineering >  Imitation of OperationCanceledException scenario with unit-test fails
Imitation of OperationCanceledException scenario with unit-test fails

Time:08-15

I use Moq 4.18.2 framework for my tests.

The RtspClient might throw an OperationCanceledException from ConnectAsync. So, I try to test this scenario. My test below throws an exception System.OperationCanceledException: The operation was canceled. and the catch (OperationCanceledException) never gets executed. What am I doing wrong here?

RTSP

public interface IRtspClient : IDisposable
{
    event EventHandler<RawFrame> FrameReceived;

    Task ConnectAsync(CancellationToken token);
    Task ReceiveAsync(CancellationToken token);
}

Method that uses IRtspClient

public async Task ConnectAsync(CancellationToken token = default)
{
    try
    {
        await _rtspClient.ConnectAsync(token).ConfigureAwait(false);
        OnConnected();
    }
    catch (OperationCanceledException ex)
    {
        OnConnectAttemptCanceled(ex);
        throw;
    }
    catch(Exception ex)
    {
        OnFailedToConnect(ex);
        throw;
    }
}

Test

[TestMethod]
public async Task ConnectAsync_Canceled()
{
    var expectedCanceledTaskStatus = true;

    var tcs = new CancellationTokenSource();
    tcs.Cancel();
    var rtspClient = new Mock<IRtspClient>();
    rtspClient
        .Setup(_ => _.ConnectAsync(It.IsAny<CancellationToken>()))
        .Returns(Task.FromException<OperationCanceledException>(new OperationCanceledException()))

    var actualCanceledTaskStatus = false;
    var camera = new MyCamera(rtspClient.Object);
    camera.FailedToConnect  = () => actualCanceledTaskStatus = true;
    await camera.ConnectAsync(tcs.Token);

    Assert.AreEqual(expectedCanceledTaskStatus, actualCanceledTaskStatus);
}

UPDATE Added missing await as suggested by @Dai, but my test still fails. Can anyone take a look at the test code?

CodePudding user response:

  • You need to await the returned Task inside the try{} block - otherwise synchronous control will immediately leave the try{} block.
  • Exceptions thrown inside an anonymous function (or local function, or lambda method, or ye olde school delegate() local) will not be caught by the catch.
  • Also, CancellationTokenSource is IDisposable, so you should change your ConnectAsync_Canceled test to wrap it in a using() block.
  • Also, don't swallow exceptions - so my code below captures both exceptions for possible investigation.

Change your code to this:

public async Task ConnectAsync( CancellationToken cancellationToken = default )
{
    try
    {
         await this.rtspClient.ConnectAsync(cancellationToken ).ConfigureAwait(false);

         this.OnConnected();
    }
    catch( OperationCanceledException cEx )
    {
        this.OnConnectAttemptCanceled( cEx );

        throw; // Re-throw so the `Task` representing *this method* (`ConnectAsync`) will report as Cancelled rather than Succeeded.
    }
    catch( Exception ex ) 
    {
        this.OnFailedToConnect( ex );

        throw; // Re-throw so the `Task` representing *this method* (`ConnectAsync`) will report as Failed rather than Succeeded.
    }
}

CodePudding user response:

I found my mistake (in addition to what @Dai noticed). I should have either put await camera.ConnectAsync from my test in try-catch or used Assert.ThrowsExceptionAsync. I chose the latter. Here is the working test:

[TestMethod]
public async Task ConnectAsync_Canceled()
{
    var expectedTaskCanceledStatus = true;

    var rtspClient = new Mock<IRtspClient>();
    rtspClient
        .Setup(_ => _.ConnectAsync(default(CancellationToken)))
        .Returns(Task.FromException(new OperationCanceledException()));

    var actualTaskCanceledStatus = false;
    var camera = new MyCamera(rtspClient.Object);
    camera.ConnectAttemptCanceled  = () => actualTaskCanceledStatus = true;

    await Assert.ThrowsExceptionAsync<OperationCanceledException>(async () => await camera.ConnectAsync());
    Assert.AreEqual(expectedTaskCanceledStatus, actualTaskCanceledStatus);
}
  • Related