views:

131

answers:

3

Am writing unit test for a multi-threading application, where I need to wait until a specific event triggered so that I know the asyn operation is done. E.g. When I call repository.add(something), I wait for event AfterChange before doing any assertion. So I write a util 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 event out of the class. Is there a way to achieve that?

UPDATE

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));
}
+3  A: 

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:

    var x = Observable.FromEvent<EventArgs>(button, "Click");
    
  • You can provide a delegate for subscription and a delegate for unsubscription:

    var x = Observable.FromEvent<EventArgs>(
          handler => button.Click += handler,
          handler => button.Click -= handler);
    

(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> :)

Jon Skeet
Am using the 2nd approach, and will try on the reflection. Rx has not approved in my company yet that I can't wait. But definitely it will be a lot easier to write test cases using Rx.
Jerry Liu
A: 

Pass in a reference to the class that publishes the event, and then hook the event via the class.

Update

For an example of testing asynchronous events, and a little framework I developed, please see my answer to:

Unit Testing an Event Firing From a Thread:

http://stackoverflow.com/questions/2337245/unit-testing-an-event-firing-from-a-thread/2693566#2693566

It goes something like this:

MyClass thingWithAsyncEvent = new MyClass();

Action test = () => { thingWithAsyncEvent.DoAsyncThing(); };

var expectedSequence = new[] { "EventName" };

EventMonitor.Assert(test, thingWithAsyncEvent, expectedSequence, TimeoutMS);

//Assert your conditions on thingWithAsyncEvent

And an article on event testing:

http://gojisoft.com/blog/2010/04/22/event-sequence-unit-testing-part-1/

chibacity
I don't think that's the point. OP wants to have a generic solution for testing any event with `EventHandler` signature.
Groo
Updated with example of generic async event testing.
chibacity
+1  A: 

Based on the syntax used in Jon Skeet's answer, this could be a custom solution:

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));
}

Usage:

AssertEventIsFired<EventArgs>(
    h => obj.EventFired += h,
    h => obj.EventFired -= h,
    () => obj.DoSomethingToFireEvent());
Groo
I am using the exactly the same work around as of now. This works fine although not elegant. but it becomes annoying when I expended it to hook up multiple events, that I need 2 lists for attach and detach..
Jerry Liu