【问题标题】:Moq - Reference parameter in virtual method doesn't update correctly when class is mockedMoq - 模拟类时,虚拟方法中的引用参数未正确更新
【发布时间】:2018-07-16 17:24:09
【问题描述】:

我已测试检查更改后引发的事件中的 ref 参数值。当我创建真正的类时测试工作正常,但是当我尝试模拟类时它失败了。简单代码示例:

[TestClass]
public class Fixture
{
    [TestMethod]
    public void CheckRealClass()
    {
        // this test passed
        var classToTest = new NumberChangerClass();
        CheckClass(classToTest);
    }

    [TestMethod]
    public void CheckMockedClass()
    {
        // this test failed
        var classToTest = new Mock<NumberChangerClass>() { CallBase = true };
        CheckClass(classToTest.Object);
    }

    private void CheckClass(NumberChangerClass numberChangerClass)
    {
        // Arrange
        var number = 1;
        numberChangerClass.StorageChanged += (sender, args) =>
        {
            Assert.AreEqual(2, number);
        };


        // Act
        numberChangerClass.SetValue(ref number, 2);
    }
}

public class NumberChangerClass
{
    public event EventHandler StorageChanged;

    public virtual void SetValue(ref int storage, int newValue)
    {
        storage = newValue;
        StorageChanged?.Invoke(this, null);
    }
}

CheckMockedClass 中,number 实际上已更改,但仅在代码从SetValue 方法退出之后。

我使用最小起订量版本 4.2.1409.1722,但它在最新版本 (4.8.1) 中也不起作用。

有没有办法让CheckMockedClass通过?

更新:

我已在 moq github 中打开问题:link

简短回答:对此无能为力。

更多细节:

您观察到的行为是 DynamicProxy(Moq 所依赖的)在方法调用拦截期间如何处理引用参数的直接结果(记录在 https://github.com/castleproject/Core/blob/master/docs/dynamicproxy-by-ref-parameters.md):在调用拦截期间,参数被缓冲在一个单独的位置并且只在拦截结束时写回,这就是为什么 refparameters 以延迟/延迟的方式更新。不幸的是,对此无能为力。

当我检查Assert.AreEqual(2, number);时,我仍然试图找到一种方法来获取更新的号码

【问题讨论】:

  • 你为什么要嘲笑被测类?
  • 这只是简单的代码。在实际情况下,我的类要复杂得多,我需要设置类的一些方法。

标签: c# unit-testing moq


【解决方案1】:

根据文档

参考Moq Quickstart

最终不得不在具体类上使事件虚拟化,因为这是 Moq 可以使用的。如果它是在接口上定义的,它会开箱即用。

public class NumberChangerClass {
    public virtual event EventHandler StorageChanged;

    public virtual void SetValue(ref int storage, int newValue) {
        storage = newValue;
        StorageChanged?.Invoke(this, null);
    }
}

为了模拟它并引发事件。

delegate void SetValueCallback(ref int storage, int newValue);

[TestMethod]
public void CheckMockedClass() {
    var classToTest = new Mock<NumberChangerClass>() { CallBase = true };
    // callbacks for methods with `ref` / `out` parameters are 
    //possible but require some work (and Moq 4.8 or later):

    classToTest
        .Setup(_ => _.SetValue(ref It.Ref<int>.IsAny, It.IsAny<int>()))
        .Callback(new SetValueCallback((ref int storage, int newValue) => {
            storage = newValue;
            classToTest.Raise(_ => _.StorageChanged += null, EventArgs.Empty);
        }));
    CheckClass(classToTest.Object);
}

这实际上是在模仿基本方法中所做的事情,因此我担心模拟被测类。

这有一种代码味道,建议检查正在测试的代码的当前设计。

【讨论】:

  • 在我的情况下,我无法将事件更改为虚拟事件,当我尝试您的解决方案时,测试通过了,因为测试没有达到 Assert.AreEqual(2, number)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-11
  • 1970-01-01
  • 1970-01-01
  • 2013-03-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多