使用 Moq 模拟类时,如何仅针对特定方法进行 CallBase?
Posted
技术标签:
【中文标题】使用 Moq 模拟类时,如何仅针对特定方法进行 CallBase?【英文标题】:When mocking a class with Moq, how can I CallBase for just specific methods? 【发布时间】:2011-02-18 20:37:18 【问题描述】:我非常感谢 Moq 的 Loose
模拟行为,它在未设置期望时返回默认值。它既方便又节省了我的代码,而且还起到了安全措施的作用:在单元测试期间不会无意中调用依赖项(只要它们是虚拟的)。
但是,当被测方法恰好是虚拟方法时,我对如何保持这些好处感到困惑。 在这种情况下,我确实想调用那个方法的真实代码,同时仍然松散地模拟类的其余部分。
我在搜索中发现的只是我可以设置mock.CallBase = true
以确保调用该方法。但是,这会影响整个班级。我不想这样做,因为它让我对隐藏调用依赖关系的类中的所有其他属性和方法进退两难:如果 CallBase 为真,那么我必须要么
-
为隐藏依赖关系的所有属性和方法设置存根 -- 即使我的测试认为它不需要关心这些依赖关系,或者
希望我不要忘记设置任何存根(并且以后不会将新的依赖项添加到代码中)- 单元测试遇到真正的依赖项的风险。
我想我想要的是:mock.Setup(m => m.VirtualMethod()).CallBase();
这样当我调用mock.Object.VirtualMethod()
时,Moq 就会调用真正的实现...
问:使用 Moq,当我模拟类以存根几个依赖项时,有什么方法可以测试虚拟方法? IE。无需诉诸 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";
【问题讨论】:
只是简要说明您/我的需求:在模拟时需要CallBase
版本,而不是 “如果没有期望覆盖成员,则调用基类实现”,即所谓的Partial Mocks,请调用mocked members的基础实现!我想将新函数命名为CallBaseOnMockedMemberOnly
:D
【参考方案1】:
由于这个问题已经很久没有人回答了,我认为它值得回答,所以我将专注于您提出的***别的问题:当被测方法恰好是虚拟的时,如何保持这些好处。
快速回答:你不能用最小起订量来做,或者至少不能直接做。但是,你可以做到。
假设您有两个行为方面,其中方面 A 是虚拟的,而方面 B 不是。这几乎反映了你在课堂上学到的东西。 B 是否可以使用其他方法;这取决于你。
目前,您的班级 Foo
正在做两件事 - A 和 B。我可以看出它们是不同的职责,只是因为您想模拟 A 并自行测试 B。
您可以:
将行为 A 移到单独的类中 依赖通过Foo
的构造函数将带有A的新类注入Foo
从 B 调用该类。
现在您可以模拟 A,并且仍然调用 B..N 的真实代码,而无需实际调用真实的 A。您可以保持 A 虚拟或通过接口访问它并模拟它。这也符合单一职责原则。
您可以使用 Foo
级联构造函数 - 使构造函数 Foo()
调用构造函数 Foo(new A())
- 因此您甚至不需要依赖注入框架来执行此操作。
希望这会有所帮助!
【讨论】:
只是为了检查:您确认如果被测方法是虚拟的,并且我们正在使用 Mock我相信 Lunivore 的答案在撰写时是正确的。
在较新版本的 Moq 中(我认为从 2013 年的 4.1 版开始),可以使用您建议的确切语法来做您想做的事情。那就是:
mock.Setup(m => m.VirtualMethod()).CallBase();
这设置了松散的模拟来调用VirtualMethod
的基本实现,而不是仅仅返回default(WhatEver)
,但只针对这个成员(VirtualMethod
)。
正如用户 BornToCode 在 cmets 中指出的那样,如果方法具有 返回类型 void
,这将不起作用。当VirtualMethod
为非void 时,Setup
调用会给出一个Moq.Language.Flow.ISetup<TMock, TResult>
,它从Moq.Language.Flow.IReturns<TMock, TResult>
继承CallBase()
方法。但是当方法为 void 时,我们会得到一个 Moq.Language.Flow.ISetup<TMock>
,而它缺少所需的 CallBase()
方法。
更新: andrew.rockwell 在下面指出,它现在适用于 void
方法,显然这已在 4.10 版(从 2018 年开始)中得到修复。
【讨论】:
我不再在日常工作中使用 C#,因此无法进行验证,但我很乐意更新已接受的答案以反映当前现实。如果其他人可以加入以确认@Jeppe 的新答案,请这样做! ——谢谢 @Daryn:我可以确认它有效,只是尝试过。阅读这个问题最终意外地帮助我理解了一个不相关的问题(为什么我所有的非虚拟方法都调用它们的基本实现?),所以我想我会回报你的帮助并试一试:) 如果 VirtualMethod() 返回 void 则不起作用。有没有办法让它也适用于 void 方法? @BornToCode 显然不是。我在回答中提到了这个缺点。感觉就像 Moq 中的一个错误(或缺失的功能)。 从今天开始为我使用void
方法【参考方案3】:
有一种方法可以调用真正的方法并且在方法为void
时有一个回调,但它真的 hacky .你必须让你的回调明确地调用它,并欺骗 Moq 调用真正的。
例如,给定这个类
public class MyInt
public bool CalledBlah get; private set;
public virtual void Blah()
this.CalledBlah = true;
您可以这样编写测试:
[Test]
public void Test_MyRealBlah()
Mock<MyInt> m = new Mock<MyInt>();
m.CallBase = true;
bool calledBlah = false;
m.When(() => !calledBlah)
.Setup(i => i.Blah())
.Callback(() => calledBlah = true; m.Object.Blah(); )
.Verifiable();
m.Object.Blah();
Assert.IsTrue(m.Object.CalledBlah);
m.VerifyAll();
关键方面是您跟踪是否已调用假版本,然后将模拟设置为不如果已调用假版本。
如果你接受 args 并且值很重要,你仍然可以做类似的事情:
public class MyInt
public List<int> CalledBlahArgs get; private set;
public MyInt()
this.CalledBlahArgs = new List<int>();
public virtual void Blah(int a)
this.CalledBlahArgs.Add(a);
[Test]
public void Test_UpdateQueuedGroups_testmockcallback()
Mock<MyInt> m = new Mock<MyInt>();
m.CallBase = true;
List<int> fakeBlahArgs = new List<int>();
m.Setup(i => i.Blah(It.Is<int>(a => !fakeBlahArgs.Contains(a))))
.Callback<int>((a) => fakeBlahArgs.Add(a); m.Object.Blah(a); )
.Verifiable();
m.Object.Blah(1);
Assert.AreEqual(1, m.Object.CalledBlahArgs.Single());
m.Verify(b => b.Blah(It.Is<int>(id => 1 == id)));
【讨论】:
有趣。但是,您设置了m.CallBase = true;
,但提问者明确表示他想要一个解决方案,您不将 CallBase
设置为 True,因为这也会影响所有其他 virtual
成员,而提问者希望避免这种情况。跨度>
以上是关于使用 Moq 模拟类时,如何仅针对特定方法进行 CallBase?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Moq 在 .NET Core 2.1 中模拟新的 HttpClientFactory