如何从派生自接口的 class 的 Fake 实例引发事件

How to raise an event from Fake instance of class derived from interface

待测代码

Public Class ObservableName
    Implements INotifyPropertyChanged

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Protected Sub RaisePropertyChanged(propertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

    Private _Name as String
    Public Property Name As String
        Get
            Return _Name
        End Get
        Set(value As String)
            _Name = value
            Me.RaisePropertyChanged(NameOf(Me.Name))
        End Set
    End Property
End Class

Public Class ViewModel
    Public ReadOnly Property MyName As ObservableName
    Public Property CountOfChanges As Integer

    Public Sub New(name As ObservableName)
        Me.MyName = name
        AddHandler Me.MyName.PropertyChanged, AddressOf Me.MyName_PropertyChanged
    End Sub

    Protected Sub MyName_PropertyChanged(sender as Object, e As PropertyChangedEventArgs)
        If e.PropertyName.Equals(NameOf(Me.MyName.Name)) = True Then
            Me.CountOfChanges += 1
        End If
    End Sub
End Class

使用 NUnitNSubstitute 进行测试。
测试更改名称(引发 PropertyChanged 事件)将更新 CountOfChanges proerty

<Test>
Public Sub CountOfChanges_NameChanged_ShouldIncreaseByOne()
  Dim previuosCount As Integer = 0
  Dim nextCount As Integer = previuos + 1
  Dim fakename As ObservableName = Substitute.For(Of ObservableName)()
  Dim vm As New ViewModel(fakename)
  vm.CountOfChanges = previuosCount 

  AddHandler vm.MyName.PropertyChanged, Raise.Event(Of PropertyChangedEventHandler)(vm.MyName, New PropertyChangedEventArgs(NameOf(vm.MyName.Name)))

  Assert.AreEqual(nextCount, vm.CountOfChanges)
End Sub

使用上面的代码永远不会引发 PropertyChanged 事件,但下一次直接使用 INotifyPropertyChanged 通过的测试将成功引发事件

<Test>
Public Sub PropertyChanged_RaiseEvent()
    Dim test As INotifyPropertyChanged = Substitute.For(Of INotifyPropertyChanged)()
    Dim isRaised As Boolean = False
    AddHandler test.PropertyChanged, Sub() isRaised = True

    AddHandler test.PropertyChanged, Raise.Event(Of PropertyChangedEventHandler)(test, New PropertyChangedEventArgs("test"))

    Assert.IsTrue(isRaised)
End Sub

你真的需要在这里使用NSubstitute并使用白盒测试吗? 如果您将 "pure technical interest" 放在一边,那么只需在测试中设置 vm.myName.Name 属性,然后断言 vm.CountOfChanges 增加会简单得多。这将是一种 "black box" 方法,它不依赖于内部实现的细节,因此更加健壮。 它仍然完全测试您的功能,只是省略了实现细节

要了解它为何无法按预期工作,请考虑以下内容。 NSubstitute 使用 Castle.DynamicProxy 来实现替换 - 使用的机制是继承。 事件是一种语言特性,通过使用具有相同签名的私有委托字段实现。该事件的全部目的是封装一个委托。 由于委托字段是私有的,您无法在派生的 class 中访问它,因此无法触发事件。 要在继承 class 中引发基础 class 事件,基础 class 必须具有引发事件的虚拟方法,继承 class 必须覆盖该事件,请参阅 this link 例如,但网上对此有很多很好的解释。