在 C# 中使用 Moq 引发复杂事件
Raising complex event using Moq in C#
以下代码应该是不言自明的:我们有一个适配器,它使用来自传输(层)的事件,它持有 MessageRegistrar(对象类型,因为我们无法分辨它的类型,主要是因为这是遗留的代码 :-) )。传输层有一个具体的事件。
我想测试一个触发事件的情况,所以..
经过几个小时的尝试弄清楚为什么它不会通过,我提出以下挑战:
[TestFixture]
public class AdaptorTests
{
public delegate void TracksEventHandler(object sender, List<int> trklst);
public class MyEventHolder
{
public virtual event TracksEventHandler EventName;
}
public interface ITransport
{
object MessageRegistrar { get; }
}
public class MyTransport : ITransport
{
private readonly MyEventHolder m_eventHolder;
public MyTransport(MyEventHolder eventHolder)
{
m_eventHolder = eventHolder;
}
public virtual object MessageRegistrar
{
get { return m_eventHolder; }
}
}
public class MyAdaptor
{
private readonly ITransport m_transport;
public MyAdaptor(ITransport transport)
{
EventTriggered = false;
m_transport = transport;
}
public void Connect()
{
MyEventHolder eventHolder = m_transport.MessageRegistrar as MyEventHolder;
if (eventHolder != null)
eventHolder.EventName += EventHolderOnEventName;
}
private void EventHolderOnEventName(object sender, List<int> trklst)
{
EventTriggered = true;
}
public bool EventTriggered { get; private set; }
}
[Test]
public void test1()
{
Mock<MyEventHolder> eventHolderMock = new Mock<MyEventHolder> {CallBase = true};
Mock<MyTransport> transportMock = new Mock<MyTransport>(eventHolderMock.Object) {CallBase = true};
MyAdaptor adaptor = new MyAdaptor(transportMock.Object);
adaptor.Connect();
MyEventHolder eventHolder = transportMock.Object.MessageRegistrar as MyEventHolder;
Mock.Get(eventHolder).Raise(eh => eh.EventName += null, new List<int>());
Assert.IsTrue(adaptor.EventTriggered);
}
[Test]
public void test2()
{
Mock<MyEventHolder> eventHolderMock = new Mock<MyEventHolder> { CallBase = true };
Mock<MyTransport> transportMock = new Mock<MyTransport>(eventHolderMock.Object) { CallBase = true };
MyAdaptor adaptor = new MyAdaptor(transportMock.Object);
adaptor.Connect();
MyEventHolder eventHolder = transportMock.Object.MessageRegistrar as MyEventHolder;
Mock.Get(eventHolder).Raise(eh => eh.EventName += null, null, new List<int>());
Assert.IsTrue(adaptor.EventTriggered);
}
}
我的问题是:为什么测试(至少其中一个)没有通过?
编辑@151217-0822 将 'adaptor.Connect()' 添加到原始 post(仍然无法解决问题)。
解决方法
感谢@Patrick Quirk:谢谢!!
对于那些遇到同样问题的人:在我了解了 Patrick-Quirk 检测到的内容并尝试了几个失败的解决方法之后,我最终添加了以下经过验证的修复程序:'eventHolder.FireEventNameForTestings(new List());':
public class MyEventHolder
{
public virtual event TracksEventHandler EventName;
public virtual void FireEventNameForTestings(List<int> trklst)
{
TracksEventHandler handler = EventName;
if (handler != null)
handler(this, trklst);
}
}
[Test]
public void test3()
{
Mock<MyEventHolder> eventHolderMock = new Mock<MyEventHolder> { CallBase = true };
Mock<MyTransport> transportMock = new Mock<MyTransport>(eventHolderMock.Object) { CallBase = true };
MyAdaptor adaptor = new MyAdaptor(transportMock.Object);
adaptor.Connect();
MyEventHolder eventHolder = transportMock.Object.MessageRegistrar as MyEventHolder;
eventHolder.FireEventNameForTestings(new List<int>());
Assert.IsTrue(adaptor.EventTriggered);
}
HTH..
似乎 CallBase
和 Raise()
有一种(对我来说)意想不到的互动。
当您将事件处理程序附加到模拟上的虚拟事件时,您会经历 this code in Moq:
if (invocation.Method.IsEventAttach())
{
var delegateInstance = (Delegate)invocation.Arguments[0];
// TODO: validate we can get the event?
var eventInfo = this.GetEventFromName(invocation.Method.Name.Substring(4));
if (ctx.Mock.CallBase && !eventInfo.DeclaringType.IsInterface)
{
invocation.InvokeBase();
}
else if (delegateInstance != null)
{
ctx.AddEventHandler(eventInfo, (Delegate)invocation.Arguments[0]);
}
return InterceptionAction.Stop;
}
您可以看到,如果 CallBase
是 true
,那么它会将您的处理程序添加到 具体对象的 事件(通过 invocation.InvokeBase()
).如果 CallBase
是 false
,它将把它添加到 mock 的调用列表中(通过 AddEventHandler
)。现在让我们看看 the code for Raise()
, which gets the event object from the Expression
and then calls DoRaise()
:
internal void DoRaise(EventInfo ev, EventArgs args)
{
// ... parameter validation
foreach (var del in this.Interceptor.InterceptionContext.GetInvocationList(ev).ToArray())
{
del.InvokePreserveStack(this.Object, args);
}
}
看到对 GetInvocationList()
的调用了吗?它从我上面提到的模拟中检索调用列表。此代码从不调用基础对象上的实际事件。
因此,似乎无法在 CallBase
设置为 true
的模拟对象上引发事件。
如果您要求 CallBase
成为 true
,我看到的唯一解决方法是向您的具体 MyEventHolder
添加一个方法来触发您的事件。显然你发布的是一个简化的例子,所以我不能给你更多的指导,但希望我已经向你展示了为什么你的东西不起作用。
以下代码应该是不言自明的:我们有一个适配器,它使用来自传输(层)的事件,它持有 MessageRegistrar(对象类型,因为我们无法分辨它的类型,主要是因为这是遗留的代码 :-) )。传输层有一个具体的事件。 我想测试一个触发事件的情况,所以..
经过几个小时的尝试弄清楚为什么它不会通过,我提出以下挑战:
[TestFixture]
public class AdaptorTests
{
public delegate void TracksEventHandler(object sender, List<int> trklst);
public class MyEventHolder
{
public virtual event TracksEventHandler EventName;
}
public interface ITransport
{
object MessageRegistrar { get; }
}
public class MyTransport : ITransport
{
private readonly MyEventHolder m_eventHolder;
public MyTransport(MyEventHolder eventHolder)
{
m_eventHolder = eventHolder;
}
public virtual object MessageRegistrar
{
get { return m_eventHolder; }
}
}
public class MyAdaptor
{
private readonly ITransport m_transport;
public MyAdaptor(ITransport transport)
{
EventTriggered = false;
m_transport = transport;
}
public void Connect()
{
MyEventHolder eventHolder = m_transport.MessageRegistrar as MyEventHolder;
if (eventHolder != null)
eventHolder.EventName += EventHolderOnEventName;
}
private void EventHolderOnEventName(object sender, List<int> trklst)
{
EventTriggered = true;
}
public bool EventTriggered { get; private set; }
}
[Test]
public void test1()
{
Mock<MyEventHolder> eventHolderMock = new Mock<MyEventHolder> {CallBase = true};
Mock<MyTransport> transportMock = new Mock<MyTransport>(eventHolderMock.Object) {CallBase = true};
MyAdaptor adaptor = new MyAdaptor(transportMock.Object);
adaptor.Connect();
MyEventHolder eventHolder = transportMock.Object.MessageRegistrar as MyEventHolder;
Mock.Get(eventHolder).Raise(eh => eh.EventName += null, new List<int>());
Assert.IsTrue(adaptor.EventTriggered);
}
[Test]
public void test2()
{
Mock<MyEventHolder> eventHolderMock = new Mock<MyEventHolder> { CallBase = true };
Mock<MyTransport> transportMock = new Mock<MyTransport>(eventHolderMock.Object) { CallBase = true };
MyAdaptor adaptor = new MyAdaptor(transportMock.Object);
adaptor.Connect();
MyEventHolder eventHolder = transportMock.Object.MessageRegistrar as MyEventHolder;
Mock.Get(eventHolder).Raise(eh => eh.EventName += null, null, new List<int>());
Assert.IsTrue(adaptor.EventTriggered);
}
}
我的问题是:为什么测试(至少其中一个)没有通过?
编辑@151217-0822 将 'adaptor.Connect()' 添加到原始 post(仍然无法解决问题)。
解决方法
感谢@Patrick Quirk:谢谢!!
对于那些遇到同样问题的人:在我了解了 Patrick-Quirk 检测到的内容并尝试了几个失败的解决方法之后,我最终添加了以下经过验证的修复程序:'eventHolder.FireEventNameForTestings(new List());':
public class MyEventHolder
{
public virtual event TracksEventHandler EventName;
public virtual void FireEventNameForTestings(List<int> trklst)
{
TracksEventHandler handler = EventName;
if (handler != null)
handler(this, trklst);
}
}
[Test]
public void test3()
{
Mock<MyEventHolder> eventHolderMock = new Mock<MyEventHolder> { CallBase = true };
Mock<MyTransport> transportMock = new Mock<MyTransport>(eventHolderMock.Object) { CallBase = true };
MyAdaptor adaptor = new MyAdaptor(transportMock.Object);
adaptor.Connect();
MyEventHolder eventHolder = transportMock.Object.MessageRegistrar as MyEventHolder;
eventHolder.FireEventNameForTestings(new List<int>());
Assert.IsTrue(adaptor.EventTriggered);
}
HTH..
似乎 CallBase
和 Raise()
有一种(对我来说)意想不到的互动。
当您将事件处理程序附加到模拟上的虚拟事件时,您会经历 this code in Moq:
if (invocation.Method.IsEventAttach())
{
var delegateInstance = (Delegate)invocation.Arguments[0];
// TODO: validate we can get the event?
var eventInfo = this.GetEventFromName(invocation.Method.Name.Substring(4));
if (ctx.Mock.CallBase && !eventInfo.DeclaringType.IsInterface)
{
invocation.InvokeBase();
}
else if (delegateInstance != null)
{
ctx.AddEventHandler(eventInfo, (Delegate)invocation.Arguments[0]);
}
return InterceptionAction.Stop;
}
您可以看到,如果 CallBase
是 true
,那么它会将您的处理程序添加到 具体对象的 事件(通过 invocation.InvokeBase()
).如果 CallBase
是 false
,它将把它添加到 mock 的调用列表中(通过 AddEventHandler
)。现在让我们看看 the code for Raise()
, which gets the event object from the Expression
and then calls DoRaise()
:
internal void DoRaise(EventInfo ev, EventArgs args)
{
// ... parameter validation
foreach (var del in this.Interceptor.InterceptionContext.GetInvocationList(ev).ToArray())
{
del.InvokePreserveStack(this.Object, args);
}
}
看到对 GetInvocationList()
的调用了吗?它从我上面提到的模拟中检索调用列表。此代码从不调用基础对象上的实际事件。
因此,似乎无法在 CallBase
设置为 true
的模拟对象上引发事件。
如果您要求 CallBase
成为 true
,我看到的唯一解决方法是向您的具体 MyEventHolder
添加一个方法来触发您的事件。显然你发布的是一个简化的例子,所以我不能给你更多的指导,但希望我已经向你展示了为什么你的东西不起作用。