I am writing unit tests for a multi-threading application, where I need to wait until a specific event is triggered so that I know the asynchronous operation is done. For example, when I call repository.add(something), I wait for event AfterChange before doing any assertion. So I wrote a utility function to do that:
public static void SyncAction(EventHandler event_, Action action_)
{
var signal = new object();
EventHandler callback = null;
callback = new EventHandler((s, e) =>
{
lock (signal)
{
Monitor.Pulse(signal);
}
event_ -= callback;
});
event_ += callback;
lock (signal)
{
action_();
Assert.IsTrue(Monitor.Wait(signal, 10000));
}
}
However, the compiler prevents from passing the event out of the class. Is there a way to achieve that?
Below is the solution using reflection.
public static void SyncAction(object target_, string event_, Action action_)
{
SyncAction(
new List<Pair<object, string>>() { new Pair<object, string>(target_, event_) },
action_);
}
public static void SyncAction(IEnumerable<Pair<object, string>> events_, Action action_)
{
var events = events_
.Select(a => new Pair<object, EventInfo>(a.First, a.First.GetType().GetEvent(a.Second)))
.Where(a => a.Second != null);
var count = events.Count();
var signal = new object();
var callback = new EventHandler((s, e) =>
{
lock (signal)
{
--count;
Monitor.Pulse(signal);
}
});
events.ForEach(a => a.Second.AddEventHandler(a.First, callback));
lock (signal)
{
action_();
while (count > 0)
{
Assert.IsTrue(Monitor.Wait(signal, 10000));
}
}
events.ForEach(a => a.Second.RemoveEventHandler(a.First, callback));
}
The problem is that events aren’t really first-class values in .NET 🙁 (Ditto properties, methods etc.)
Reactive Extensions handles this in two ways:
You can provide the name of the event and the target, and it will use reflection… something like this:
You can provide a delegate for subscription and a delegate for unsubscription:
(The exact methods may be slightly different, but that’s the general idea.)
Of course, if you’re happy to use Reactive Extensions yourself, you could use those methods and make your test use
IObservable<T>🙂