【问题标题】:How to mock or fake a CancellationToken?如何模拟或伪造 CancellationToken?
【发布时间】:2022-01-07 16:40:39
【问题描述】:

我想对以下方法进行单元测试:

    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;
            }
        }
    }

我需要一个 CancellationToken 的模拟,以便我可以设置它的 ThrowIfCancellationRequested 方法来引发异常。但是,CancellationToken 是一个结构,我不能使用 Moq。

有人知道如何模拟 CancellationToken 吗?

【问题讨论】:

  • 在调用 ReadFileAsStream 之前,是否有什么阻止您使用实际的 CancellationToken 并取消它?
  • 是的,我想写一个单元测试。根据定义,单元测试应该隔离所有依赖项。如果我使用实际的 CancellationToken 并取消它 - 那么这将是一个集成测试。
  • @MiBuena,在我看来,您对此毫无意义的教条主义。如果您的方法将这些作为参数,您是否应该尝试弄清楚如何模拟 intDateTime?当然不是——那不是“集成”测试。
  • 如果您说取消是终止外部服务的套接字,那么可以肯定:这可能是一个集成测试;这里?不;那仍然是一个单元测试;无论哪种方式:我们怎么称呼它重要吗?(修辞:我在这里寻找的答案是“不”)。唯一重要的是我们测试代码,您可以使用CancellationTokenSource

标签: c# unit-testing cancellation-token


【解决方案1】:

首先,我认为您不能甚至不应该模拟CancellationToken,因为它是一个结构。这就像嘲笑Int

您可以测试您的逻辑的一种方法是创建一个CancellationTokenSoure 并在构造函数中传递延迟参数并将令牌从源传递到您的方法。之后断言该方法行为正确。

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

这样您的_readWrapper.GetStreamReader() 将延迟/等待更长的时间,以至于CancellationTokenSourcecancellationToken.ThrowIfCancellationRequested(); 中设置的延迟将在调用时抛出 OperationCancelledException。

【讨论】:

  • 字符串是一种引用类型,而不是结构;-)
  • 是的,我知道。我只是想说明一点:D 我已经编辑了答案
猜你喜欢
  • 2014-01-01
  • 2011-06-29
  • 2016-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多