Reputation: 571
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
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