c# - 如何测试事件是否包含事件处理程序?

标签 c# unit-testing events moq

我想测试 A 类的 RegisterEventHandlers() 方法将其方法之一注册为类事件的 EventHandler B。我怎样才能做到这一点?如果重要的话,我正在使用最小起订量。

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

是否有一个干净的解决方案?

最佳答案

您可以在声明事件的类之外获取事件的调用列表 - 但它涉及反射。下面是一个代码示例,展示了在调用 a.RegisterEventHandlers 之后如何确定将哪些方法(在目标实例 a 上)添加到事件 b.TheEvent ()。将下面的代码粘贴到代码文件中并添加到窗体或控制台项目中: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);
         }


      }
   }




关于c# - 如何测试事件是否包含事件处理程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6481324/

相关文章:

c# - 如何确保在每个循环上创建一个新的 Person?

typescript - Jasmine-ts 抛出关于包子路径的错误

delphi - 当拖动窗口或单击下拉菜单时,VCL TTimer 停止

c# - 检查本地用户所属的组

c# - Null To Boolean IValueConverter 不工作

c# - 无法使用 PRISM 5、MVVM 和 EF 6 在 WPF 中刷新 DataGrid

javascript - 如何向用js创建的元素添加事件?

python - 如何测试在模拟时是否使用 "self"调用实例的方法?

reactjs - 如何模拟 React setState 进行 API 测试

JavaScript 函数运行了两次?