【问题标题】:Unit test. Why Mock returns different value in second loop iteration?单元测试。为什么 Mock 在第二次循环迭代中返回不同的值?
【发布时间】:2019-03-06 08:57:27
【问题描述】:

我对以下对象有一个 Mock,其工作是收集传感器数据。它实现了这个接口:

public interface ISensorDataCollector
{
    List<int> CollectSensorData(int amountOfValues);        
}

在测试里面我有如下安排:

// ARRANGE
var collector = new Mock<ISensorDataCollector>() { CallBase = true };
// Mock SensorDataCollector
collector.Setup((x) => x.CollectSensorData(10)
         .Returns(new List<int> { 1,2,3,4,5,6,7,8,9});
myProcess.AdwSensorDataCollector = collector.Object;

// ACT
myProcess.CollectSensorDataRepeatIfFails(5);

要测试的方法有一个收集数据的 while 循环。简单版本如下所示:

public ISensorDataCollector SensorDataCollector { get; set; }

public void CollectSensorDataRepeatIfFails(int counterForRepeatedMeasurement)
{        
    do
    {
        List<int> values = this.SensorDataCollector.CollectSensorData(10); 

        values.Clear();

        counterForRepeatedMeasurement--;
    } while (counterForRepeatedMeasurement >= 0);
}

问题:从this.AdwSensorDataCollector.CollectSensorData(10); 行的第二次迭代返回一个空列表。但我希望它每次都能返回我在设置中指定的值:

collector.Setup((x) => x.CollectSensorData(10)
         .Returns(new List<int> { 1,2,3,4,5,6,7,8,9});

我想这与values.Clear(); 有关,因为如果我删除列表清除。 CollectSensorDataRepeatIfFails 的返回值在所有迭代中保持不变,我的问题就消失了。但这只是一个猜测。我真的很想知道为什么第二次调用没有返回指定的返回值。

问题:我错过了什么?该设置是否仅适用于模拟方法的 1 次调用?或者是否有一个我忘记按下的按钮使其按预期方式运行?为什么清空列表会影响第二次调用mocked方法的返回值?有人可以解释一下这个问题吗?

在询问之前我做了一些研究,但我只能找到解释如何使模拟在下次调用时返回不同值的帖子。但是没有使用循环出现问题的帖子。如果有重复的小费,我也会很高兴。

干杯

【问题讨论】:

    标签: c# unit-testing mocking moq


    【解决方案1】:

    我错过了什么?该设置是否仅适用于模拟方法的 1 次调用?

    实际上这是模拟的预期行为。从某种意义上说,设置是可变的,您可以通过引用更改捕获的参数或返回值。 values.Clear(); 正是这样做的。为了避免这个问题,只需通过提供工厂来推迟列表的创建。像这样的:

    collector.Setup((x) => x.CollectSensorData(10))
        .Returns<int>((i) => new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
    

    【讨论】:

    • 太好了,谢谢。这很有意义。我稍微修改了您的解决方案:.Returns(() =&gt; { return new List&lt;int&gt; { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; }); 我只能接受 1 个答案,所以我会接受这个答案,因为它还有一个附加说明的解决方案。我对这两个答案都投了赞成票
    【解决方案2】:

    这是预期行为,因为您正在使用该列表的引用。无论您是从模拟还是其他地方返回它。

    collector.Setup((x) => x.CollectSensorData(10)
        .Returns(new List<int> { 1,2,3,4,5,6,7,8,9});
    

    您正在注册referenceList&lt;int&gt;

    List&lt;int&gt; values = this.AdwSensorDataCollector.CollectSensorData(10);

    因此,在此调用中,您将收到对完全相同List&lt;int&gt;对象的引用。

    values.Clear()

    调用它会删除模拟应该返回的完全相同的列表的所有项目。

    您可以在此处阅读有关引用类型的更多信息: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/reference-types

    更新: 您可以使用.Callback(() =&gt; new List&lt;int&gt;() { 1, 2, 3}) 代替Returns。在这种情况下,每次调用模拟函数时都会调用回调。

    【讨论】:

    • 太好了,谢谢。这很有意义。我的期望是每次调用CollectSensorData 都会返回一个新列表。但如果 mock 只是简单地保留引用 at 再次重用它,那当然是预期的行为。
    • @MongZhu 我更新了问题,因此您可以将其用于您的预期行为。
    • 我只能接受 1 个答案,Johnny Bravo 更快,但我对这两个答案都投了赞成票。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-16
    • 2021-09-25
    • 1970-01-01
    • 2017-11-03
    • 2014-11-25
    • 2023-03-03
    相关资源
    最近更新 更多