界面模拟上的 Moq CallBase

Moq CallBase on mock of interface

假设我们有以下设置:

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();

输出为:

Called Child.Bar()
Called 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> 的干净解决方案?

只要您在模拟接口,就无法访​​问或了解有关真实 classes 的信息,这就解释了为什么您没有得到任何输出(但我想您明白这一点)。

不幸的是,如果您选择模拟接口(根据定义,接口没有行为),使事情发生的唯一方法是按照您的方式设置方法。

另一种 "dirty" 方法是对 child 和基础 class 使用方法扩展,前提是方法的内容仅使用 public 属性和方法。

public static class ChildExtension
{
    public static void Bar(this Child child)
    {
        Console.WriteLine("Called Child.Bar()");
    }
}

你走错方向了

Mock 的存在是为了帮助进行单元测试。例如,如果你想测试 class 的方法 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);
    }
}

在这种情况下,为了测试 DataSaver 的方法 Save 您将在单元测试中调用它,但是这样做时您将面临的问题是该方法实际上将尝试使用存储库对象保存数据。除非你发送一个 假存储库 你的单元测试将在你每次 运行 时保存数据,这不是单元测试应该做的。它不应该 运行 来自具体 IRepository object 的方法,但它仍然应该调用它的方法。

在这种情况下,为了避免保存 object,您可以做的是制作另一个 class,它实现 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

这里 Mock 有帮助

为每个单元测试制作一个假的 class 是相当困难的,这就是替代模拟提供的:

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,编写单元测试没有很大的影响,但它可以揭示错误,并让代码只做它应该做的事情。

在你的情况下,如果你真的想调用具体的实际方法object:

child.Setup(c => c.Bar()).Callback(() =>
{
    Console.WriteLine("Called Child.Bar()");
});

那么这意味着您甚至不应该尝试使用模拟来重现您模拟的 object 完全相同的实现。如果它做的事情与实际的 object?

相同,那么模拟的用途是什么?

在这种情况下,您应该删除模拟并创建一个具体的 Child object,因为您不想模拟 child 的行为,您正在尝试实现它使用模拟 ,它删除了模拟本身的功能

简单的答案是在单元测试中使用具体的object:

var child = new Child();

child.Bar();
child.Foo();