为事件的添加访问器编写单元测试

时间:2018-10-26 14:31:54

标签: c# unit-testing events

我有一个从第三方程序集调用的方法,它用作我们的应用程序输入点。此方法引发事件MyEvent。为了确保同一事件处理程序仅被注册一次且仅注册一次,我为addremove实现了自己的逻辑:

class MyEditorExtension
{
    private EventHandler<MyArgs> myEvent
    public EventHandler<MyArgs> MyEvent
    {
        add
        {
            if(this.myEvent == null || this.myEvent.GetInvocationList().All(x => !x.Equals(value))
                this.myEvent += value;
        }
        remove { this.myEvent -= value; }
    }

    // this method is called from ArcMap
    public void OnCreate()
    {
        ...
        MyEvent();
    }
}

OnCreate很大,无法安全地重构为较小的,可测试的单元。但是,我想检查我的事件定义是否确实应做的事情。为了做到这一点,我尝试两次注册完全相同的方法,并检查它是否执行了两次:

[TestFixture]
public class ExtensionTest
{
    int numCalls;

    [Test]
    public void Test_Register_Handler_Twice()
    {
        var target = new MyExtension();
        target.MyEvent += myHandler;
        target.MyEvent += myHandler;
        target.MyEvent(new MyArgs());
        Assert.AreEqual(1, this.numCalls);
    }
    private void(MyArgs args) { this.numCalls ++; }
}

当然,以上内容不会编译,因为我无法在类之外引发一个事件。我还想避免仅出于测试事件的目的而引入RaiseEvent方法。

所以我想知道是否有任何方法可以实现这一目标,例如使用反射?

1 个答案:

答案 0 :(得分:1)

实际上,可以使用反射。但是,这很hacky。实际上,这是测试遗留代码时的常见问题。此解决方案的灵感来自此帖子:https://stackoverflow.com/a/12000050/2528063

您必须知道,事件只不过是围绕私有(隐藏)委托域的add-和remove方法,就像属性只不过是get-和set一样-围绕私有(也隐藏)后备场的方法。话虽如此,您可以访问该委托,该委托通常与您的事件具有相同的名称:

var delegateField = typeof(MyExtension).GetField("MyEvent", BindingFlags.NonPublic)?.GetValue(target);

在特殊情况下,我们拥有自己的访问器,因此,委托人的名称直接在MyExtension的源代码中提供。因此,我们编写了这个略有不同的版本:

var delegateField = typeof(MyExtension).GetField("myEvent", BindingFlags.NonPublic)?.GetValue(target);

现在我们有了我们的支持委托,我们可以轻松地调用它:

delegateField?.DynamicInvoke(new MyArgs());

我们当然应该添加一些健全性检查,例如在这种情况下,该代表被重命名,因此无法找到,但是我想您明白了。