2010-05-18 4 views
2

J'écris des tests unitaires pour une application multi-thread, où je dois attendre qu'un événement spécifique soit déclenché pour que je sache que l'opération asynchrone est terminée. Par exemple, lorsque j'appelle repository.add(something), j'attends l'événement AfterChange avant de faire une assertion. J'ai donc écrit une fonction utilitaire pour le faire:Comment passer un événement en paramètre en C#

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

Cependant, le compilateur empêche de faire passer l'événement hors de la classe. Y a-t-il un moyen d'y parvenir?

Voici la solution utilisant la réflexion.

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

Répondre

4

Le problème est que les événements ne sont pas vraiment des valeurs de première classe dans .NET :((propriétés Ditto, méthodes, etc.)

Reactive Extensions poignées de deux façons:

  • Vous pouvez fournir le nom de l'événement et la cible, et il utilisera la réflexion ... quelque chose comme ceci:

    var x = Observable.FromEvent<EventArgs>(button, "Click"); 
    
  • Vous pouvez fournir un délégué à la souscription et un délégué pour désabonnement:

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

(. Les méthodes exactes peuvent être légèrement différentes, mais c'est l'idée générale)

Bien sûr, si vous Nous sommes heureux d'utiliser Reactive Extensions vous-même, vous pouvez utiliser ces méthodes et faire votre test utiliser IObservable<T> :)

+0

Am en utilisant la 2ème approche, et essayer de la réflexion. Rx n'a pas encore approuvé dans mon entreprise que je ne peux pas attendre. Mais certainement, il sera beaucoup plus facile d'écrire des cas de test en utilisant Rx. –

3

Basé sur la syntaxe utilisée dans Jon Skeet's answer, cela pourrait être une solution personnalisée:

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

Utilisation:

AssertEventIsFired<EventArgs>(
    h => obj.EventFired += h, 
    h => obj.EventFired -= h, 
    () => obj.DoSomethingToFireEvent()); 
+0

J'utilise exactement le même travail autour de maintenant. Cela fonctionne bien mais pas élégant. mais ça devient agaçant quand je l'ai dépensé pour connecter plusieurs évènements, que j'ai besoin de 2 listes pour les attacher et les détacher. –

Questions connexes