简体   繁体   中英

Write unit-test for add-accessor of event

I have a method which is called from a 3rd party assembly and serves as our application-entry point. This method raises our event MyEvent . In order to ensure the same event-handler is only registered once and only once, I implemented my own logic for add and remove :

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 is quite huge and can´t safely be refactored into smaller, testable units. However I want to check if my event-definition really does what it is supposed to do. In order to do so I tried to register the exact same method twice and check if it was executed twice:

[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 ++; }
}

Of course the above won´t compile because I can´t raise an event outside its class. I´d also like to avoid introducing a RaiseEvent -method solely for the sake of testing the event.

So I wonder if there´s any way to achieve this, eg using reflection?

Indeed, it is possible using reflection. However this is quite hacky. In fact this is a common problem when testing legacy-code as this one. This solution was inspired by this post: https://stackoverflow.com/a/12000050/2528063

You have to know that an event is nothing but an add - and remove -method around a private (hidden) delegate-field, just like a property is nothing but a get- and set-method around a private (also hidden) backing-field. Having said this you can access that delegate, which usually has the exact same name as your event:

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

In our special case we have our own accessors and therefor the name of the delegate is provided directly within the source-cde of MyExtension . Thus we write this slightly different version:

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

Now we have our backing delegate, which we can easily invoke:

delegateField?.DynamicInvoke(new MyArgs());

Of course we should add some sanity-checks, eg for the case the delegate was renamed and thus couldn´t be found, but I guess you get the point.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM