【问题标题】:Raising complex event using Moq in C#在 C# 中使用 Moq 引发复杂事件
【发布时间】:2015-12-16 16:54:49
【问题描述】:

以下代码应该是自我解释的:我们有一个适配器,它使用来自传输(层)的事件,它保存 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);
    }

}

我的问题是:为什么测试(至少其中一个)不能通过?

EDIT @151217-0822 在原始帖子中添加了“adaptor.Connect()”(仍然无法解决问题)。

解决方法

感谢@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..

【问题讨论】:

  • 您是否错过了给Connect()的电话?
  • 在示例中是,在实际代码中 - 否。不过,添加 connect 并不能解决问题。受到您的见解的启发,我最终添加了“eventHolder.FireEventNameForTestings”来解决该问题,并且它奏效了。谢谢!

标签: c# unit-testing tdd moq mstest


【解决方案1】:

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(),它从Expression获取事件对象,然后调用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 的模拟对象上引发事件。

如果您需要 CallBasetrue,我看到的唯一解决方法是向具体的 MyEventHolder 添加一个方法来触发您的事件。显然,您发布的是一个简化的示例,因此我无法为您提供更多指导,但希望我已经向您展示了为什么您所拥有的内容不起作用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-23
    • 2016-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多