我们假设我们有以下设置:
public interface IBase
{
void Foo();
}
public class Base : IBase
{
public virtual void Foo()
{
Console.WriteLine("Called Base.Foo()");
}
}
public interface IChild : IBase
{
void Bar();
}
public class Child : Base, IChild
{
public virtual void Bar()
{
Console.WriteLine("Called Child.Bar()");
}
}
模拟Child
对象时,一切正常:
var child = new Mock<Child> { CallBase = true };
child.Object.Bar();
child.Object.Foo();
输出是:
叫做Child.Bar()
叫做Base.Foo()
但是当模拟IChild
界面没有打印到控制台时:
var child = new Mock<IChild> { CallBase = true };
child.Object.Bar();
child.Object.Foo();
假设我无法模拟Child
对象,因为没有无参数构造函数(依赖注入)。
我知道我可以做以下事情:
child.Setup(c => c.Bar()).Callback(() =>
{
// Copy paste bar-method body
});
child.Setup(c => c.Foo()).Callback(() =>
{
// Copy paste foo-method body
});
但那会非常难看
是否有使用Mock<IChild>
的干净解决方案?
答案 0 :(得分:1)
只要你嘲笑界面,就没有关于真实类的访问或信息,这就解释了为什么你没有得到任何输出(但我想你明白了)。
不幸的是,如果你选择模拟一个接口(根据定义没有行为),那么让事情发生的唯一方法就是以你的方式设置方法。
如果方法的内容仅使用公共属性和方法,则另一种“脏”方法是对子类和基类使用方法扩展。
public static class ChildExtension
{
public static void Bar(this Child child)
{
Console.WriteLine("Called Child.Bar()");
}
}
答案 1 :(得分:0)
你的方向错误
模拟存在以帮助进行单元测试。例如,如果要测试使用Save()
上的包装器的类的方法DbContext
,如下所示:
interface IRepository
{
void PersistData(object dataToBeSaved);
}
class DataSaver
{
private IRepository _repository;//this object's method PersistData makes a call to a database
public DataSaver(IRepository repository)
{
_repository = repository;
}
public void Save(object dataToBeSaved)
{
_repository.PersistData(dataToBeSaved);
}
}
在这种情况下,为了测试Save
的方法DataSaver
,您将在单元测试中对其进行调用,但是在执行此操作时您将面临的问题是该方法实际上会尝试使用repository objet保存数据。除非您发送虚假存储库,否则每次运行时单元测试都会保存数据,这不是单元测试应该做的事情。它不应该从具体的IRepository
对象运行方法,但它仍然应该调用它的方法。
在这种情况下,为避免保存对象,您可以做的是创建另一个仅用于测试IRepository
的类:
class DummyRepository : IRepository
{
public object DataJustSaved { get; set; }
public void PersistData(object dataToBeSaved)
{
DataJustSaved = dataToBeSaved;
}
}
现在,在您的单元测试中,您将执行以下操作:
var dummyRepository = new DummyRepository();
var dataSaver = new DataSaver(dummyRepository);
var savedObject = new Object();
var expectedObject = savedObject;
dataSaver.Save(savedObject);//test the save method
var actualObject = dummyRepository.DataJustSaved;
Assert.AreEqual(expectedObject, actualObject);//verify that the data was passed to the PersistData method
模拟帮助
为每个单元测试制作假类是非常困难的,这就是模拟提供的替代方案:
var dummyRepository = new Mock<IRepository>();
var dataSaver = new DataSaver(dummyRepository.Object);
var savedObject = new Object();
dataSaver.Verify(x => x.PersistData(savedObject), Times.Once());// just make sure the method PersistData was invoked with the expected data and only once.
Mock存在的原因是为你做出非常聪明的dummies
,编写单元测试而不会产生很大的影响但是可以揭示错误,并保持代码只做它应该做的事情。
在您的情况下,如果您真的想要调用具体对象的实际方法:
child.Setup(c => c.Bar()).Callback(() =>
{
Console.WriteLine("Called Child.Bar()");
});
然后它意味着你甚至不应该尝试使用mock来重现你模拟的对象的完全相同的实现。如果模拟与实际对象做同样的事情,那么模拟的用途是什么?
在这种情况下,您应该删除模拟并创建一个具体的Child
对象,因为您不想模拟一个孩子的行为,您正试图使用模拟来实现它,这将删除模拟本身的功能。
简单的答案是在单元测试中使用具体对象:
var child = new Child();
child.Bar();
child.Foo();