简体   繁体   English

如何验证某个事件已取消订阅模拟

[英]How to verify an event has been unsubscribed from on a mock

Moq version: 3.1.416.3 Moq版本:3.1.416.3

We found a bug caused by an event not being unsubscribed. 我们发现了一个因未被取消订阅而导致的错误。 I am trying to write a unit test to verify that the event is unsubscribed from. 我正在尝试编写一个单元测试来验证该事件是否已取消订阅。 Is it possible to verify this using Mock<T>.Verify(expression) ? 是否可以使用Mock<T>.Verify(expression)来验证这一点?

My initial thought was: 我最初的想法是:

mockSource.Verify(s => s.DataChanged -= It.IsAny<DataChangedHandler>());

But apparently 但显然

An expression tree may not contain an assignment operator 表达式树可能不包含赋值运算符

Then I tried 然后我试了一下

mockSource.VerifySet(s => s.DataChanged -= It.IsAny<DataChangedHandler>());

But that gives me 但这给了我

System.ArgumentException: Expression is not a property setter invocation. System.ArgumentException:Expression不是属性setter调用。

How can I verify that the unsubscribe has taken place? 如何验证取消订阅是否已经发生?

How the event is used 如何使用该事件

public class Foo
{
    private ISource _source;

    public Foo(ISource source)
    {
        _source = source;
    }

    public void DoCalculation()
    {
        _source.DataChanged += ProcessData;

        var done = false;

        while(!done)
        {
            if(/*something is wrong*/)
            {
                Abort();
                return;
            }
            //all the things that happen
            if(/*condition is met*/)
            {
                done = true;
            }
        }

        _source.DataChanged -= ProcessData;
    }

    public void Abort()
    {
        _source.DataChanged -= ProcessData; //this line was added to fix the bug
         //other cleanup
    }

    private void ProcessData(ISource)
    {
        //process the data
    }
}

Ignore the convoluted nature of the code, we're dealing with signals from external hardware. 忽略代码的复杂性,我们处理来自外部硬件的信号。 This actually makes sense for the algorithm. 这实际上对算法有意义。

Assuming that ProcessData does something meaningful, ie either changes the state of the SUT (system under test) in a meaningful/observable way, or acts on the event args, just raising the event on the mock and inspecting if the change happen should be enough. 假设ProcessData执行了一些有意义的操作,即以有意义/可观察的方式更改SUT(被测系统)的状态,或者对事件args进行操作,只需在模拟上引发事件并检查更改是否发生就足够了。

Example with changing state: 更改状态的示例:

....
public void ProcessData(ISource source)
{
   source.Counter ++;
}

...
[Test]
.....

sut.DoWork();
var countBeforeEvent = source.Count;
mockSource.Raise(s => s.DataChanged += null, new DataChangedEventArgs(fooValue));
Assert.AreEqual(countBeforeEvent, source.Count);

Of course, the above should be adapted to whatever implementation you have in ProcessData. 当然,上面的内容应该适用于ProcessData中的任何实现。

When doing unit testing, you should not be concerned about the implementation details (ie if some event is unsubscribed) and should not test that, but about behavior - ie if you raise an event, does something happen. 在进行单元测试时,您不应该关注实现细节(即某些事件是否取消订阅)并且不应该测试,而是关于行为 - 即如果您举起事件,则会发生某些事情。 In your case it's enough to verify that ProcessData is not called. 在您的情况下,足以验证ProcessData未被调用。 Of course you need another test that demonstrates that the event is called during normal operation (or certain conditions). 当然,您需要另一个测试来演示在正常操作(或某些条件)期间调用事件。

EDIT: the above is with using Moq. 编辑:以上是使用Moq。 But ... Moq is a tool, and like any tool it should be used for the right job. 但是...... Moq是一种工具,就像任何工具一样,它应该用于正确的工作。 If you really need to test that the "-=" is invoked, then you should pick a better tool - like implementing your own stub of the ISource. 如果你真的需要测试“ - =”被调用,那么你应该选择一个更好的工具 - 比如实现你自己的ISource存根。 The following example has pretty useless class under test which just subscribes and then unsubscribes from the event, just to demonstrate how you can test. 下面的例子有一个非常无用的测试类,它只是订阅然后取消订阅事件,只是为了演示如何测试。

using System;
using NUnit.Framework;
using SharpTestsEx;

namespace StackOverflowExample.Moq
{
    public interface ISource
    {
        event Action<ISource> DataChanged;
        int InvokationCount { get; set; }
    }

    public class ClassToTest
    {
        public void DoWork(ISource source)
        {
            source.DataChanged += this.EventHanler;
        }

        private void EventHanler(ISource source)
        {
            source.InvokationCount++;
            source.DataChanged -= this.EventHanler;
        }
    }

    [TestFixture]
    public class EventUnsubscribeTests
    {
        private class TestEventSource :ISource
        {
            public event Action<ISource> DataChanged;
            public int InvokationCount { get; set; }

            public void InvokeEvent()
            {
                if (DataChanged != null)
                {
                    DataChanged(this);
                }
            }

            public bool IsEventDetached()
            {
                return DataChanged == null;
            }
        }

        [Test]
        public void DoWork_should_detach_from_event_after_first_invocation()
        {
            //arrange
            var testSource = new TestEventSource();
            var sut = new ClassToTest();
            sut.DoWork(testSource);

            //act
            testSource.InvokeEvent();
            testSource.InvokeEvent(); //call two times :)

            //assert
            testSource.InvokationCount.Should("have hooked the event").Be(1);
            testSource.IsEventDetached().Should("have unhooked the event").Be.True();
        }
    }
} 

there is a dirty way to get the invocationList from a event outside the target class, althaught this should only be used for testing or debugging purposes as it breaks the purpose of events. 有一种脏的方法可以从目标类之外的事件中获取invocationList,因此它只能用于测试或调试目的,因为它会破坏事件的目的。

This only works if the event is not implemented with a customer implementation (add/remove), If the event has event Accessors the eventInfo2FieldInfo will return null. 这仅在事件未使用客户实现(添加/删除)实现时才有效。如果事件具有事件访问者,则eventInfo2FieldInfo将返回null。

 Func<EventInfo, FieldInfo> eventInfo2FieldInfo = eventInfo => mockSource.GetType().GetField(eventInfo.Name, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField);
 IEnumerable<MulticastDelegate> invocationLists = mockSource.GetType().GetEvents().Select(selector => eventInfo2FieldInfo(selector).GetValue(mockSource)).OfType<MulticastDelegate>();

now you got the invocation Lists for all events of the target class, and should be able to assert if a special event got unsubscribed. 现在,您获得了目标类的所有事件的调用列表,并且应该能够断言特殊事件是否已取消订阅。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何验证是否已添加事件处理程序 - How to verify that an event handler has been added 如何验证在模拟DbContext上已调用RemoveRange? - How can I verify that RemoveRange has been called on a mock DbContext? 从.NET验证一个DLL具有有效的签名并且没有被修改 - From .NET verify a dll has valid signature and has not been modified 如何验证函数是否已在C#中缓存? - How can I verify if a function has been cached in C#? moq-如果类吞下异常,如何验证方法未被调用 - moq - how to verify method has not been called if the class swallows exceptions 如何使用FakeItEasy验证方法参数以及是否进行了调用 - How to verify method parameters with FakeItEasy as well as that a call has been made 如何验证是否已使用Moq设置字符串数组中的值 - How to verify that a value in a string array has been set using Moq 如何断言已经使用FakeItEasy订阅了一个事件? - How to Assert that an Event Has been Subscribed To with FakeItEasy? 如何验证模拟对象是否被“遗忘”? - How to verify that mock object was “gotten”? Excel VSTO如何检查在激发WorkbookBeforeClose事件后是否从对话框中触发了取消按钮? - Excel VSTO how to check if the Cancel button has been triggered from the dialog after the WorkbookBeforeClose event is fired?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM