如何防止虚拟方法被模拟
本文关键字:模拟 方法 虚拟 何防止 | 更新日期: 2023-09-27 17:51:07
我们有一个基类为INotifyPropertyChanged
提供了一些默认实现(这个类被许多其他类使用,不能轻易改变):
public class ObservableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// this is virtual so derived classes can override it (rarely used, but it is used)
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
现在我有一个接口和一个抽象基类派生自ObservableBase
和实现该接口提供一些默认实现(主要是属性):
public interface INamedTrigger
{
string Name { get; }
void Execute();
}
public abstract class ObservableNamedTriggerBase : ObservableBase, INamedTrigger
{
private string _Name;
public string Name
{
get { return _Name; }
set { _Name = value; OnPropertyChanged("Name"); }
}
public abstract void Execute();
}
现在我想对ObservableNamedTriggerBase
的默认实现进行单元测试(使用NUnit和RhinoMocks):
[TestFixture]
public class ObservableNamedTriggerBaseTest
{
[Test]
public void TestNameChangeRaisesPropertyChanged()
{
var prop = "";
var mocks = new MockRepository();
var namedBase = mocks.PartialMock<ObservableNamedTriggerBase>();
namedBase.PropertyChanged += (s, e) => prop = e.PropertyName;
namedBase.Name = "Foo";
Assert.AreEqual("Name", prop);
}
}
不幸的是,这个测试失败了,因为Rhino似乎覆盖了ObservableBase
的虚拟OnPropertyChanged
方法。
这个SO问题和Rhino文档表明PartialMock
应该完全而不是执行此操作。另一方面,这个SO问题的答案表明Rhino总是覆盖虚拟方法,而不管mock的类型是什么。
所以文档是错的吗?我怎样才能正确地进行测试呢?
Update:如果我创建自己的模拟类,为抽象方法提供虚拟实现,那么测试通过,但我想使用Rhino模拟来节省我做那件事的工作。
不确定您使用的是哪个版本,但似乎您将RR (Record-Replay)语法使用与最近的AAA (Arrange-Act-Assert)混合在一起。基本上,这样做:
var repo = new MockRepository();
var mock = repo.PartialMock<ObservableNamedTriggerBase>();
还要求您至少这样做:
repo.ReplayAll();
缺少这些,将导致模拟行为不正常(如果您也设置期望/存根,可能需要调用repo.Record()
)。考虑到可以使用现代语法(从Rhino 3.5开始),这是不必要的努力:
// no repository needed at all
var mock = MockRepository.GeneratePartialMock<ObservableNamedTriggerBase>();
MockRepository
上的新静态方法在底层负责记录重放设置。
一个想法是创建一个测试子类,其中方法是不可重写的。
public abstract class TestingObservableNamedTriggerBase : ObservableNamedTriggerBase {
protected override sealed void OnPropertyChanged(string propertyName) {
base.OnPropertyChanged(propertyName);
}
}
然后通过这个子类进行测试。
您可以使用CallBase
布尔属性来告诉moq是使用原始的虚拟方法还是用默认值覆盖它们。正如它在属性的摘要中所说:
CallBase Summary:如果没有匹配的设置,是否会为模拟类调用基成员虚拟实现。默认为false。
所以用这个:
mock.CallBase = true;
您可以使用Rhinomocks的CallOriginalMethod方法来调用原始方法。
namedBase.Expect(x => x.OnPropertyChanged(Arg<string>.Is.Anything))
.CallOriginalMethod(OriginalCallOptions.NoExpectation);
如果测试下的类包含来自测试程序集的其他程序集,您可能需要使OnPropertyChanged
方法为protected internal
,并为测试程序集添加InternalVisibleToAttribute
。
您也知道,添加InternalVisibleToAttribute("DynamicProxyGenAssembly2")
来模拟内部类型。