Moq CallBase模拟界面

时间:2017-01-18 17:32:32

标签: c# moq

我们假设我们有以下设置:

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

2 个答案:

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