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.