Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/279.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何验证已从模拟中取消订阅事件_C#_Unit Testing_Event Handling_.net 3.5_Moq - Fatal编程技术网

C# 如何验证已从模拟中取消订阅事件

C# 如何验证已从模拟中取消订阅事件,c#,unit-testing,event-handling,.net-3.5,moq,C#,Unit Testing,Event Handling,.net 3.5,Moq,最小起订量版本:3.1.416.3 我们发现一个由未取消订阅的事件引起的错误。我正在尝试编写一个单元测试,以验证该事件是否已取消订阅。是否可以使用Mock.verify(expression)来验证这一点 我最初的想法是: mockSource.Verify(s => s.DataChanged -= It.IsAny<DataChangedHandler>()); 忽略代码的复杂性质,我们正在处理来自外部硬件的信号。这实际上对算法是有意义的。从目标类之外的事件获取调用列表有

最小起订量版本:3.1.416.3

我们发现一个由未取消订阅的事件引起的错误。我正在尝试编写一个单元测试,以验证该事件是否已取消订阅。是否可以使用
Mock.verify(expression)
来验证这一点

我最初的想法是:

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

忽略代码的复杂性质,我们正在处理来自外部硬件的信号。这实际上对算法是有意义的。

从目标类之外的事件获取调用列表有一种肮脏的方法,尽管这只应用于测试或调试目的,因为它破坏了事件的目的

这仅在事件未通过客户实施(添加/删除)实施时有效, 如果事件具有事件访问器,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>();
Func eventInfo2FieldInfo=eventInfo=>mockSource.GetType().GetField(eventInfo.Name,BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField);
IEnumerable invocationLists=mockSource.GetType().GetEvents().Select(选择器=>eventInfo2FieldInfo(选择器).GetValue(mockSource)).OfType();

现在,您已经获得了目标类所有事件的调用列表,并且应该能够断言某个特殊事件是否被取消订阅。

假设
ProcessData
做了一些有意义的事情,即以有意义/可观察的方式更改SUT(测试中的系统)的状态,或者对事件参数进行操作,只要在模拟上引发事件并检查是否发生了更改就足够了

更改状态的示例:

....
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);
当然,以上内容应该适合ProcessData中的任何实现

在进行单元测试时,您不应该关心实现细节(即,如果某个事件未被订阅),也不应该测试该细节,而应该关注行为——即,如果您引发了一个事件,是否发生了什么。在您的情况下,只需验证未调用
ProcessData
。当然,您需要另一个测试来证明在正常操作(或某些条件)期间调用该事件

编辑:以上是使用最小起订量。但是Moq是一种工具,和任何工具一样,它应该用于正确的工作。如果您确实需要测试是否调用了“-=”,那么您应该选择一个更好的工具,比如实现您自己的ISource存根。下面的示例中有一个非常无用的类正在测试,它只是订阅然后取消订阅事件,只是为了演示如何进行测试

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();
        }
    }
} 
使用系统;
使用NUnit.Framework;
使用性;
命名空间StackOverflowExample.Moq
{
公共接口源
{
事件动作数据改变;
int InvokationCount{get;set;}
}
公共类类测试
{
公共空道具(ISource源)
{
source.DataChanged+=this.EventHanler;
}
私有void EventHanler(ISource源)
{
InvokationCount++;
source.DataChanged-=this.EventHanler;
}
}
[测试夹具]
公共类事件取消订阅测试
{
私有类TestEventSource:ISource
{
公共事件行动数据变更;
public int InvokationCount{get;set;}
public void InvokeEvent()
{
如果(DataChanged!=null)
{
数据更改(本);
}
}
公共文件已披露()
{
返回DataChanged==null;
}
}
[测试]
在第一次调用()之后,公共void DoWork应该从事件中分离
{
//安排
var testSource=new TestEventSource();
var sut=new ClassToTest();
sut.DoWork(测试源);
//表演
testSource.InvokeEvent();
testSource.InvokeEvent();//调用两次:)
//断言
testSource.InvokationCount.Should(“已钩住事件”).Be(1);
testSource.IsEventDetached().Should(“已解除事件挂钩”).Be.True();
}
}
} 

是否有任何措施阻止向事件添加失败案例?您可以使用类似Assert.Fail()委托的方式订阅事件,如果事件未取消订阅,则应触发该委托。我不认为您可以从外部解析当前处理程序的事件:@AdamKewley这将有助于确保所有处理程序都已删除,但我需要确保仅删除一个处理程序。尽管如此,我可以修改代码以使事件平坦化。那可能真的有用。你能再解释一下吗?谁/什么人参加了活动?这是你的SUT吗?是SUT负责拆卸吗?你能展示一些使用场景吗?@SunnyMilenov我添加了一个示例看看这个有趣的例子。我会尝试一下。遗憾的是,
eventInfo2FieldInfo
只会返回null。也许你错过了什么。我使用完全相同的代码来确定我的活动是否被正确取消订阅。它给了我预期的结果。我复制了你的代码,并将其调整为与
Mock
一起工作,我得到了空引用异常。我也用一个非模拟对象进行了尝试,得到了同样的结果。我很好奇,明天我将发布一个完整的示例SUT代表什么?@MattEllen:正在测试的系统-即您正在测试的类。
....
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);
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();
        }
    }
}