【问题标题】:Verifying a mocked method uses specific CancellationTokenSouce which is also mocked验证模拟方法使用特定的 CancellationTokenSouce,该方法也被模拟
【发布时间】:2012-09-04 21:40:48
【问题描述】:

我正在尝试验证特定的 CancellationTokenSource 是否用作方法调用中的实际参数。

        public void DataVerification(Object sender, EventArgs e)
        {
            _entity.PopulateEntityDataVerificationStage(_view.DataTypeInputs, _view.ColumnNameInputs, _view.InitialRow, _view.FinalRow, _view.CurrencyPair, _view.CsvFilePath, _view.ErrorLogFilePath);

            //...

            CancellationTokenSource tempCsvFileVerificationCancellation = new CancellationTokenSource();

            _source.Source = tempCsvFileVerificationCancellation;

           //Want to verify that TempCsvFileVerificationCancellation.Token is passed into the following method.
            _verify.SetupCsvFileVerification(_entity, tempCsvFileVerificationCancellation.Token);

           //...
        } 

以下是我的测试:

    [Test]
    public void DataVerification_SetupCsvFileVerification_CorrectInputs()
    {
        Mock<IMainForm> view = new Mock<IMainForm>();

        Mock<IUserInputEntity> entity = new Mock<IUserInputEntity>();

        Mock<ICsvFileVerification> verify = new Mock<ICsvFileVerification>();
        verify.Setup(x => x.SetupCsvFileVerification(It.IsAny<UserInputEntity>(), It.IsAny<CancellationToken>()));

        CancellationTokenSource cts = new CancellationTokenSource();

        Mock<ICancellationTokenSource> source = new Mock<ICancellationTokenSource>();
        source.SetupSet(x => x.Source = It.IsAny<CancellationTokenSource>()).Callback<CancellationTokenSource>(value => cts = value);
        source.SetupGet(x => x.Source).Returns(cts);
        source.SetupGet(x => x.Token).Returns(cts.Token);

        MainPresenter presenter = new MainPresenter(view.Object, entity.Object, verify.Object, source.Object);

        presenter.DataVerification(new object(), new EventArgs());

        verify.Verify(x => x.SetupCsvFileVerification(entity.Object, source.Object.Token));
    }

报错信息如下:

预期至少对模拟调用一次,但从未执行过:x => x.SetupCsvFileVerification(.entity.Object, (Object).source.Object.Token) 未配置任何设置。

source代表的类如下:

public interface ICancellationTokenSource
{
    void Cancel();

    CancellationTokenSource Source { get; set; }

    CancellationToken Token { get; }
}

public class CancellationTokenSourceWrapper : ICancellationTokenSource
{
    private CancellationTokenSource _source;

    public CancellationTokenSourceWrapper(CancellationTokenSource source)
    {
        _source = source;
    }

    public CancellationTokenSource Source
    {
        get
        {
            return _source;
        }
        set
        {
            _source = value;
        }
    }


    public CancellationToken Token 
    {
        get
        {
            return Source.Token;
        }
    }

    public void Cancel()
    {
        _source.Cancel();
    }
}

当我逐步完成单元测试时,cts 确实被分配了 TempCsvFileVerificationCancellation 的值。 source 中的 Token 属性,返回 Source.Token。我不知道我做错了什么。

任何指针/帮助将不胜感激。

谢谢

编辑

【问题讨论】:

  • 您似乎没有使用 DataVerification 方法向您的类提供验证对象,并且在测试中不清楚您如何创建调用验证的验证模拟,以及哪个然后抛出异常。
  • 嗨,大卫,你绝对正确,我曾尝试将其剪裁一点以使其更易于理解,但意外删除了验证,对此感到抱歉。我已经编辑了代码,现在应该可以了。

标签: c# c#-4.0 moq cancellationtokensource


【解决方案1】:

乍一看,它看起来应该可以工作,但实际上你可以尝试:

把verify上的匹配条件改成It.IsAny(), It.IsAny(),看看有没有被调用。 - 如果没有,请通过代码调试并查看发生了什么 - 如果匹配,那就是匹配的问题

试试entity.Object, It.IsAny()看看UserInputEntity有没有错。

如果 UserInputEntity 没问题,则在源设置上放置一个回调。它不需要做任何事情,但允许您在调用 SetupCsvFileVerification 时检查正在使用的值。

更新
我想我找到了它,它与我的以下观点之一有关。您将 cts 初始化为一个值。它需要有一个初始值,因为设置

source.SetupGet(x => x.Source).Returns(cts); 
source.SetupGet(x => x.Token).Returns(cts.Token); 

没有它会失败,因为它会立即评估 cts.Token。这意味着这将从测试中定义的 CancellationTokenSource 返回令牌,不是在生产代码中定义的令牌(并使用回调存储)。

为确保您使用新的 cts 值,您应该将设置更改为

source.SetupGet(x => x.Source).Returns(() => cts);
source.SetupGet(x => x.Token).Returns(() => cts.Token);

这将推迟评估直到使用,即在集合的回调执行之后。

添加细节
问题在于评估的时机

假设在测试设置中创建了一个 cts X,它有一个令牌 A。
然后,您将 Source 设置为返回 cts,并将令牌设置为返回 cts.Token
现在对这些进行评估,并告知获取返回 X 和 A。

在运行时,cts 被使用回调的一组覆盖(将其称为 Y,令牌为 B),这是验证使用的 B 值,因此失败。


通过将设置更改为使用 lambda,我们告诉他们使用 '当您被调用时 cts 指向的任何值',所以现在的顺序是

设置 get - 不评估 CTS 或令牌值 通话验证
- 使用回调设置 cts
- 获取源 => 评估,使用新设置的值 (Y)
- 获取令牌 => 评估,使用新设置的值 (B)
验证 => 与 B 比较,通过

其他想法

是否存在 CancellationTokenSource 只是为了支持测试?

如果是这样,另一种方法是尝试用 ICancellationTokenProvider 替换它,这将替换您的生产代码中的 new CancellationTokenSource(); 调用。这将允许您更轻松地将特定的 CancellationToken 注入代码中,从而验证 SetupCsvFileVerification()。

小问题 - 随意忽略

此外,除非您使用严格的行为设置

verify.Setup(x => x.SetupCsvFileVerification(It.IsAny<UserInputEntity>(), 
                                             It.IsAny<CancellationToken>()));

是多余的。它不返回任何内容,因此它不能用作存根,并且您稍后会显式验证调用。

同样,不需要将“cts”初始化为一个值,将其设置为 null 就足够了,因为设置源的回调应该填充它。

【讨论】:

  • 感谢您的回复。将 verify.Verify 更改为以下允许测试通过。 “verify.Verify(x => x.SetupCsvFileVerification(entity.Object, It.IsAny()));”当实际调用 SetupCsvFileVerification() 时,我从自动窗口附加了一张图片。我尝试使用回调似乎失败了。
  • 不存在cancellationTokenSource 允许用户暂停任务,因为它是一个长时间运行的过程。
  • FFS,我整天都在不断检查我是否在左上角有一个红色通知,我才刚刚看到你实际上编辑了你的帖子。 AlanT ud 这个人,测试有效:)。我仍然不完全理解为什么它不使用通过回调分配的新版本。回调是否没有改变 cts 的值,所以它不应该也改变 Source 和 Token 属性返回的值吗?我已将您的答案标记为答案,但如果您对上述问题有任何意见,请欣赏一些见解。再次感谢您的帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多