MiBuena
MiBuena

Reputation: 571

How to mock or fake a CancellationToken?

I have the following method which I would like to unit test:

    public async IAsyncEnumerable<string> ReadFileAsStream([EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        using (var reader = _readerWrapper.GetStreamReader("File.csv"))
        {
            await reader.ReadLineAsync();

            string? line;
            while ((line = await reader.ReadLineAsync()) != null)
            {
                cancellationToken.ThrowIfCancellationRequested();
                yield return line;
            }
        }
    }

I need a mock for CancellationToken, so that I can set up its ThrowIfCancellationRequested method to throw an exception. However, CancellationToken is a struct and I can not use Moq.

Does anyone have an idea how CancellationToken can be mocked?

Upvotes: 0

Views: 3886

Answers (1)

Givko
Givko

Reputation: 370

First of all I don't think you can or even should Mock the CancellationToken as it is a struct. This is like mocking Int.

One way you can test your logic is to create a CancellationTokenSoure and passing the delay parameter in the constructor and pass the token from the source to your method. After which asserting that the method behaves correctly.

i.e.

public interface IReaderWrapper
{
    public StreamReader GetStreamReader(string path);
}

public class Reader
{
    private readonly IReaderWrapper _readerWrapper;

    public Reader(IReaderWrapper readerWrapper)
    {
        _readerWrapper = readerWrapper;
    }

    public async IAsyncEnumerable<string> ReadFileAsStream(
           [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        using (var reader = _readerWrapper.GetStreamReader("File.csv"))
        {
            await reader.ReadLineAsync();

            string? line;
            while ((line = await reader.ReadLineAsync()) != null)
            {
                cancellationToken.ThrowIfCancellationRequested();
                yield return line;
            }
        }
    }
}


[TestClass]
public class Test
{
    [TestMethod]
    [ExpectedException(typeof(OperationCanceledException))]
    public async Task ReadFileAsStream_CancellationTokenIsCnacelled_ShouldThrowCancellationException()
    {
        var mockedReader = new Mock<IReaderWrapper>();
        mockedReader
            .Setup(s => s.GetStreamReader(It.IsAny<string>()))
            .Returns(() =>
            {
                //Delay before returning
                Task.Delay(TimeSpan.FromMilliseconds(500));
                //return streamReader
            });
        // set delay time after which the CancellationToken will be canceled
        var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(100)); 

        var reader = new Reader(mockedReader.Object);

        // Should throw OperationCanceledException
        var result = reader.ReadFileAsStream(cancellationTokenSource.Token);
    }
}

This way your the _readWrapper.GetStreamReader() will delay/wait longer that the set delay in the CancellationTokenSource and cancellationToken.ThrowIfCancellationRequested(); will throw OperationCancelledException when it is invoked.

Upvotes: 1

Related Questions