【问题标题】:Moq Using Lazy Loading in ConstructorMoq 在构造函数中使用延迟加载
【发布时间】:2012-11-01 21:11:39
【问题描述】:

我今天遇到了一个奇怪的问题 - 我正在围绕 SIM 对象编写单元测试。它断言当 SIM 对象更新并且剩余的 PIN 尝试已更改时,它将调用特定方法。测试如下:

[Test]
public void TestUpdateSimInfoWithPinAttemptsChangedCallsOnPinAttemptsRemaining()
{
    var info = new SimPinInfo {PinAttemptsRemaining = 10};

    var sim = new Mock<Sim>(info);

    info.PinAttemptsRemaining = 2;

    sim.Object.UpdateSimInfo(info);

    sim.Verify(s => s.FireOnPinAttemptsRemaining(), Times.Once());
}

因此,模拟 SIM 对象的创建剩余 10 次 PIN 尝试。然后 SimPinInfo 对象将 PinAttemptsRemaining 的值减少到 2,然后再传递给 UpdateSimInfo() 方法。

SIM 构造函数(为清楚起见进行了修剪):

internal Sim(SimPinInfo info) : this()
{
     _pinAttemptsRemaining = info.PinAttemptsRemaining;
     _pukAttemptsRemaining = info.PukAttemptsRemaining;
     ......
}

还有UpdateSimInfo() 方法(已修整):

internal void UpdateSimInfo(SimPinInfo info)
{
    lock(_locker)
    {
        if (_pinAttemptsRemaining != info.PinAttemptsRemaining)
        {
            Log("PinAttemptsRemaining changed");
            _pinAttemptsRemaining = info.PinAttemptsRemaining;
            FireOnPinAttemptsRemaining();
        }
        .....
    }
}

一个非常简单的测试 - 应该发生的是上面的 if 语句将为真(剩余的 pin 尝试已更改),因此将触发 OnPinAttemptsRemaining 事件。然而,测试失败了(虽然不是一直 - 当我慢慢地通过代码时它通过了!)。发生的事情是 if 语句是错误的 - _pinAttemptsRemaininginfo.PinAttemptsRemaining 都是 2。看起来 SIM 模拟实际上没有按预期创建 - 当 info.PinAttemptsRemaining 是 10 时。

为了证明这一点,我添加了一条评论:

var sim = new Mock<Sim>(info);
info.PinAttemptsRemaining = 2;
Console.WriteLine("SIM's pin attempts = " + sim.Object.PinAttemptsRemaining);

我还在 SIM 对象的构造函数中放置了一个断点。跨过Console.WriteLine 行时,断点被击中,而不是new Mock... 行。所以在需要之前不会创建对象。

我相信这被称为延迟加载或延迟评估。

对此行为有多种解决方法 - 我最终创建了一个新的 SimPinInfo 对象以传递给 UpdateSimInfo()

以前有人遇到过这种行为吗?我找不到任何对它的引用。

【问题讨论】:

    标签: c# unit-testing moq lazy-loading lazy-evaluation


    【解决方案1】:

    据我所知,您正在尝试测试 Mock。

    sim.Object.UpdateSimInfo(info);
    

    Mocks 旨在替代依赖项,您不能使用它们来替代您要测试的代码。如果没有看到整个代码,我猜你甚至不需要 Mock 来测试这种行为。假设 FireOnPinAttemptsRemaining 引发了一个事件,我推荐这样的测试方法:

    [Test]
    public void TestUpdateSimInfoWithPinAttemptsChangedCallsOnPinAttemptsRemaining()
    {
        int eventFiredCount = 0;
        var info = new SimPinInfo {PinAttemptsRemaining = 10};
        var sim = Sim(info);
        sim.OnPinAttemptsRemaining += (sender, e) => { eventFiredCount++; };
    
        info.PinAttemptsRemaining = 2;
        sim.UpdateSimInfo(info);
    
        Assert.AreEqual(1, eventFiredCount);
    }
    

    我不是 100% 使用事件处理程序,因为我不知道 Fire 方法是否引发事件或事件名称,但它应该会给你一个想法。

    【讨论】:

    • 谢谢史蒂夫。我从实际等待不同线程上的事件的单元测试开始,使用监视器等。下一步看起来像你的代码,它工作得很好,我最终在我的问题中使用了 Mock 代码。我可能会回到您的风格,但请您解释一下“替代依赖项/替代代码”评论。干杯。
    • 可能是最简单的解释方式。假设您有一个包装数据访问的服务。数据服务。它直接与您的数据库对话。您还有其他代码使用 DataService 来检索和保存信息。现在您要测试一些需要从 DataService 获取数据的代码。模拟允许您通过模拟数据服务来测试与数据服务隔离的代码。例如,我将为数据服务提供一个名为 IDataService 的合同接口。通过使用 Mock,您可以设置预期场景,而无需“真实”数据连接。
    猜你喜欢
    • 1970-01-01
    • 2013-12-27
    • 1970-01-01
    • 2011-02-27
    • 1970-01-01
    • 2012-04-12
    • 1970-01-01
    • 1970-01-01
    • 2021-04-05
    相关资源
    最近更新 更多