如何从派生自接口的 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
使用 NUnit
和 NSubstitute
进行测试。
测试更改名称(引发 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 例如,但网上对此有很多很好的解释。
待测代码
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
使用 NUnit
和 NSubstitute
进行测试。
测试更改名称(引发 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 例如,但网上对此有很多很好的解释。