使用Moq模拟类时,如何使用CallBase来获取特定方法?

Dar*_*ryn 37 unit-testing moq

我非常感谢Moq的Loose模拟行为,在没有设定期望时返回默认值.这很方便,可以节省代码,也可以作为一种安全措施:在单元测试期间不会无意中调用依赖关系(只要它们是虚拟的).

但是,当被测方法恰好是虚拟时,我对如何保持这些好处感到困惑.
在这种情况下,我确实想要为这一个方法调用真正的代码,同时仍然对该类的其余部分进行松散的模拟.

我在搜索中找到的就是我可以设置mock.CallBase = true以确保调用该方法.但是,这会影响整个班级.我不想那样做,因为它让我陷入了隐藏调用依赖关系的类中所有其他属性和方法的两难境地:如果CallBase是真的那么我必须要么

  1. 为隐藏依赖项的所有属性和方法设置存根 - 即使我的测试认为它不需要关心这些依赖项,或者
  2. 希望我不要忘记设置任何存根(并且以后没有新的依赖项添加到代码中) - 风险单元测试达到真正的依赖关系.

我想我想要的是:
mock.Setup(m => m.VirtualMethod()).CallBase();
所以当我打电话时mock.Object.VirtualMethod(),Moq会调用真正的实现......

问:有了Moq,有什么方法可以测试一个虚方法,当我嘲笑这个类只存在几个依赖项时?即不使用CallBase = true并且必须存根所有依赖项?


用于说明的示例代码
(使用MSTest,InternalsVisibleTo DynamicProxyGenAssembly2)

在以下示例中,TestNonVirtualMethod传递但是TestVirtualMethod失败 - 返回null.

public class Foo
{
    public string NonVirtualMethod() { return GetDependencyA(); }
    public virtual string VirtualMethod() { return GetDependencyA();}

    internal virtual string GetDependencyA() { return "! Hit REAL Dependency A !"; }
    // [... Possibly many other dependencies ...]
    internal virtual string GetDependencyN() { return "! Hit REAL Dependency N !"; }
}

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestNonVirtualMethod()
    {
        var mockFoo = new Mock<Foo>();
        mockFoo.Setup(m => m.GetDependencyA()).Returns(expectedResultString);

        string result = mockFoo.Object.NonVirtualMethod();

        Assert.AreEqual(expectedResultString, result);
    }

    [TestMethod]
    public void TestVirtualMethod() // Fails
    {
        var mockFoo = new Mock<Foo>();
        mockFoo.Setup(m => m.GetDependencyA()).Returns(expectedResultString);
        // (I don't want to setup GetDependencyB ... GetDependencyN here)

        string result = mockFoo.Object.VirtualMethod();

        Assert.AreEqual(expectedResultString, result);
    }

    string expectedResultString = "Hit mock dependency A - OK";
}
Run Code Online (Sandbox Code Playgroud)

Jep*_*sen 14

我相信Lunivore的答案在撰写时是正确的.

在较新版本的Moq中(我认为从2013年开始的4.1版本),您可以使用您提出的确切语法来执行您想要的操作.那是:

mock.Setup(m => m.VirtualMethod()).CallBase();
Run Code Online (Sandbox Code Playgroud)

这将松散的模拟设置为调用基本实现,VirtualMethod而不仅仅是返回default(WhatEver),但VirtualMethod仅限于此成员().


正如用户BornToCode在注释中注释的那样,如果方法具有返回类型void,则这将不起作用.当VirtualMethod非void时,Setup调用给出一个Moq.Language.Flow.ISetup<TMock, TResult>继承CallBase()方法的方法Moq.Language.Flow.IReturns<TMock, TResult>.但是当方法无效时,我们会得到一个Moq.Language.Flow.ISetup<TMock>缺少所需CallBase()方法的方法.

  • @BornToCode显然不是.我在答案中提到了这个缺点.它几乎感觉像是Moq中的一个bug(或缺少的功能). (2认同)

Lun*_*ore 8

由于没有人多年来回答这个问题,我认为值得回答,我将集中讨论你提出的最高级问题:当被测方法碰巧是虚拟时,如何保持这些好处.

快速回答:你不能用Moq,或者至少不能直接使用Moq.但是,你可以做到.

假设你有两个方面的行为,其中方面A是虚拟的而方面B则不是.这几乎反映了你在课堂上的成就.B可以使用其他方法吗?由你决定.

目前,你的班级Foo正在做两件事 - A和B.我可以说它们是单独的责任,因为你想要模拟A并自己测试B.

您可以:而不是试图模拟虚拟方法而不嘲笑其他任何东西,而不是:

  • 将行为A移动到单独的类中
  • 依赖注入新的类与A到Foo通过Foo的构造函数
  • 从B调用该类.

现在你可以模拟A,并且仍然可以调用B..N的真实代码,而无需实际调用真实的A.你可以保持虚拟或通过接口访问它并模拟它.这也符合单一责任原则.

您可以使用Foo- 使构造函数Foo()调用构造函数Foo(new A())- 级联构造函数- 因此您甚至不需要依赖注入框架来执行此操作.

希望这可以帮助!