我正在为多线程应用程序编写单元测试,我需要等到特定事件被触发才能知道异步操作已经完成。例如,当我调用repository.add(something)
时,我会在执行任何断言之前等待事件AfterChange。所以我写了一个实用函数来做到这一点:
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));
}
}
但是,编译器阻止将事件传递出类。有没有办法实现这个目标?
以下是使用反射的解决方案。
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));
}
答案 0 :(得分:4)
问题是事件在.NET中不是真正的第一类值:((同上属性,方法等)
Reactive Extensions以两种方式解决这个问题:
您可以提供事件的名称和目标,它将使用反射......如下所示:
var x = Observable.FromEvent<EventArgs>(button, "Click");
您可以提供订阅的委托和取消订阅的委托:
var x = Observable.FromEvent<EventArgs>(
handler => button.Click += handler,
handler => button.Click -= handler);
(确切的方法可能略有不同,但这是一般的想法。)
当然,如果您乐意自己使用Reactive Extensions,可以使用这些方法并使用IObservable<T>
进行测试:)
答案 1 :(得分:3)
根据Jon Skeet's answer中使用的语法,这可能是一个自定义解决方案:
public static void AssertEventIsFired<T>(
Action<EventHandler<T>> attach,
Action<EventHandler<T>> detach,
Action fire) where T : EventArgs
{
AutoResetEvent fired = new AutoResetEvent(false);
EventHandler<T> callback = new EventHandler<T>(
(s, e) =>
{
detach(callback);
fired.Set();
});
attach(callback);
fire();
Assert.IsTrue(fired.WaitOne(10000));
}
用法:
AssertEventIsFired<EventArgs>(
h => obj.EventFired += h,
h => obj.EventFired -= h,
() => obj.DoSomethingToFireEvent());