如何测试事件是否包含事件处理程序

本文关键字:是否 包含 事件处理 程序 事件 何测试 测试 | 更新日期: 2023-09-27 18:00:18

我想测试类ARegisterEventHandlers()方法是否将其一个方法注册为类B上事件的EventHandler。我该怎么做?如果这很重要的话,我正在使用moq。

  • 我认为没有办法从类外检查事件处理程序委托(如果我错了,请纠正我)
  • 如果我能触发事件,然后断言我的回调被调用,那就太好了,但如果我模拟A类的接口(并为回调设置期望值),那么我就失去了RegisterEventHandlers()的实现,这是我首先测试的方法
  • 模拟B类的事件将是最好的选择,但我不知道我必须拦截什么方法才能做到这一点。有没有办法为事件设置mock,并拦截+=方法调用

有干净的解决方案吗?

如何测试事件是否包含事件处理程序

您可以在声明事件的类之外获得事件的调用列表,但它涉及反射。下面是一个代码示例,显示了如何确定将哪些方法(在目标实例a上)添加到事件b.TheEvent在调用a.RegisterEventHandlers()之后。将下面的代码粘贴到代码文件中,并添加到窗体或控制台项目中:Test Test=new Test();测验Run()

using System;
using System.Reflection;
using System.Diagnostics;
using System.Collections.Generic;
   public class A
   {
      B m_b = new B();
      public void RegisterEventHandlers()
      {
         m_b.TheEvent += new EventHandler(Handler_TheEvent);
         m_b.TheEvent += new EventHandler(AnotherHandler_TheEvent);
      }
      public A()
      { 
         m_b.TheEvent += new EventHandler(InitialHandler_TheEvent);
      }
      void InitialHandler_TheEvent(object sender, EventArgs e)
      { }
      void Handler_TheEvent(object sender, EventArgs e)
      { }
      void AnotherHandler_TheEvent(object sender, EventArgs e)
      { }
   }
   public class B
   {
      public event EventHandler TheEvent;
      //{
      //   //Note that if we declared TheEvent without the add/remove methods, the
      //   //following would still generated internally and the underlying member
      //   //(here m_theEvent) can be accessed via Reflection. The automatically
      //   //generated version has a private field with the same name as the event
      //   //(i.e. "TheEvent")
      //   add { m_theEvent += value; }
      //   remove { m_theEvent -= value; }
      //}
      //EventHandler m_theEvent; //"TheEvent" if we don't implement add/remove

      //The following shows how the event can be invoked using the underlying multicast delegate.
      //We use this knowledge when invoking via reflection (of course, normally we just write
      //if (TheEvent != null) TheEvent(this, EventArgs.Empty)
      public void ExampleInvokeTheEvent()
      {
         Delegate[] dels = TheEvent.GetInvocationList();
         foreach (Delegate del in dels)
         {
            MethodInfo method = del.Method;
            //This does the same as ThisEvent(this, EventArgs.Empty) for a single registered target
            method.Invoke(this, new object[] { EventArgs.Empty });
         }
      }
   }

   public class Test
   {
      List<Delegate> FindRegisteredDelegates(A instanceRegisteringEvents, B instanceWithEventHandler, string sEventName)
      {
         A a = instanceRegisteringEvents;
         B b = instanceWithEventHandler;
         //Lets assume that we know that we are looking for a private instance field with name sEventName ("TheEvent"), 
         //i.e the event handler does not implement add/remove.
         //(otherwise we would need more reflection to determine what we are looking for)
         BindingFlags filter = BindingFlags.Instance | BindingFlags.NonPublic;
         //Lets assume that TheEvent does not implement the add and remove methods, in which case
         //the name of the relevant field is just the same as the event itself
         string sName = sEventName; //("TheEvent")
         FieldInfo fieldTheEvent = b.GetType().GetField(sName, filter);
         //The field that we get has type EventHandler and can be invoked as in ExampleInvokeTheEvent
         EventHandler eh = (EventHandler)fieldTheEvent.GetValue(b);
         //If the event handler is null then nobody has registered with it yet (just return an empty list)
         if (eh == null) return new List<Delegate>();

         List<Delegate> dels = new List<Delegate>(eh.GetInvocationList());
         //Only return those elements in the invokation list whose target is a.
         return dels.FindAll(delegate(Delegate del) { return Object.ReferenceEquals(del.Target, a); });
      }
      public void Run()
      {
         A a = new A();
         //We would need to check the set of delegates returned before we call this
         //Lets assume we know how to find the all instances of B that A has registered with
         //For know, lets assume there is just one in the field m_b of A.
         FieldInfo fieldB = a.GetType().GetField("m_b", BindingFlags.Instance | BindingFlags.NonPublic);
         B b = (B)fieldB.GetValue(a);
         //Now we can find out how many times a.RegisterEventHandlers is registered with b
         List<Delegate> delsBefore = FindRegisteredDelegates(a, b, "TheEvent");
         a.RegisterEventHandlers();
         List<Delegate> delsAfter = FindRegisteredDelegates(a, b, "TheEvent");
         List<Delegate> delsAdded = new List<Delegate>();
         foreach (Delegate delAfter in delsAfter)
         {
            bool inBefore = false;
            foreach (Delegate delBefore in delsBefore)
            {
               if ((delBefore.Method == delAfter.Method)
                  && (Object.ReferenceEquals(delBefore.Target, delAfter.Target)))
               {
                  //NOTE: The check for Object.ReferenceEquals(delBefore.Target, delAfter.Target) above is not necessary 
                  //     here since we defined FindRegisteredDelegates to only return those for which .Taget == a)
                  inBefore = true;
                  break;
               }
            }
            if (!inBefore) delsAdded.Add(delAfter);
         }
         Debug.WriteLine("Handlers added to b.TheEvent in a.RegisterEventHandlers:");
         foreach (Delegate del in delsAdded)
         {
            Debug.WriteLine(del.Method.Name);
         }

      }
   }


当嘲笑B时,像这样声明EventHandler:

public class B : IB
{
  public int EventsRegistered;
  public event EventHandler Junk
  {
     add
     {
        this.EventsRegistered++;
     }
     remove
     {
        this.EventsRegistered--;
     }
  }
}

我不确定moq是否允许这样做,但我相信你可以创建自己的mock类。

您不能从类外访问事件委托是正确的,这是C#语言中的一个限制。

测试这一点最直接的方法是模拟类B,然后引发它的事件,然后观察引发事件的副作用。这与您想要的略有不同,但它展示了类的A行为,而不是它的实现(这是您的测试应该努力做到的)。

为了实现这一点,类B必须是可模拟的,并且它公开的事件也必须是虚拟的。如果事件没有被声明为虚拟的,Moq就无法拦截事件。或者,如果B是一个接口,请确保在那里声明了事件。

public interface IEventProvider
{
    event EventHandler OnEvent;
}
public class Example
{
    public Example(IEventProvider e)
    {
        e.OnEvent += PerformWork;
    }
    private void PerformWork(object sender, EventArgs e)
    {
        // perform work
        // event has an impact on this class that can be observed
        //   from the outside.  this is just an example...
        VisibleSideEffect = true;
    }
    public bool VisibleSideEffect
    {
       get; set;
    }
}
[TestClass]
public class ExampleFixture
{
    [TestMethod]
    public void DemonstrateThatTheClassRespondsToEvents()
    {
        var eventProvider = new Mock<IEventProvider>().Object;
        var subject = new Example(eventProvider.Object);
        Mock.Get(eventProvider)
            .Raise( e => e.OnEvent += null, EventArgs.Empty);
        Assert.IsTrue( subject.VisibleSideEffect, 
                       "the visible side effect of the event was not raised.");
    }
}

如果真的需要测试实现,还有其他机制可用,例如手动的test Spy/test Double,或基于反射的策略来获取委托列表。我希望您应该更关心类A的事件处理逻辑,而不是它的事件处理程序分配。毕竟,如果A类没有对事件做出响应并对其做些什么,那么作业就不重要了。

我对单元测试了解不多,但也许这个链接可以给你一些想法。请注意,virtual关键字也适用于此。

我认为moq没有这种能力-如果你准备购买一个工具,我建议你使用Typemock Isolator,它可以验证对象上的任何方法是否被调用,包括事件处理程序-请查看链接。