在 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..

似乎 CallBaseRaise() 有一种(对我来说)意想不到的互动。

当您将事件处理程序附加到模拟上的虚拟事件时,您会经历 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;
}

您可以看到,如果 CallBasetrue,那么它会将您的处理程序添加到 具体对象的 事件(通过 invocation.InvokeBase() ).如果 CallBasefalse,它将把它添加到 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 添加一个方法来触发您的事件。显然你发布的是一个简化的例子,所以我不能给你更多的指导,但希望我已经向你展示了为什么你的东西不起作用。