繁体   English   中英

模拟接口上的 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();

输出是:

称为 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>的干净解决方案?

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

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

如果方法的内容仅使用公共属性和方法,则另一种“肮脏”的方法是对子类和基类使用方法扩展。

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

你走向错误的方向

Mock 的存在是为了帮助进行单元测试。 例如,如果您想测试在DbContext使用包装器的类的Save()方法,如下所示:

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对象运行一个方法,但它仍然应该调用它的方法。

在这种情况下,为了避免保存对象,您可以做的是创建另一个实现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 有帮助

为每个单元测试创​​建一个假类是非常困难的,这就是替代模拟提供的:

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

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

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

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

var child = new Child();

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM