我非常感谢Moq的Loose
模拟行为,在没有设定期望时返回默认值。方便并节省代码,它也可以作为一种安全措施:在单元测试期间不会无意中调用依赖项(只要它们是虚拟的)。
然而,当被测方法恰好是虚拟时,我对如何保持这些好处感到困惑 在这种情况下,我做想要为这一个方法调用真实代码,同时仍然对该类的其余部分进行松散的模拟。
我在搜索中发现的是,我可以设置mock.CallBase = true
以确保调用该方法。但是,这会影响整个班级。我不想这样做,因为它让我陷入了隐藏调用依赖关系的类中所有其他属性和方法的困境:如果CallBase为true,那么我必须
我认为我想要的是:
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";
}
答案 0 :(得分:10)
我相信Lunivore的答案在撰写时是正确的。
在较新版本的Moq(我认为从2013年开始的4.1版本)中,您可以使用您提出的确切语法来执行您想要的操作。那就是:
mock.Setup(m => m.VirtualMethod()).CallBase();
这会设置松散的模拟来调用VirtualMethod
的基本实现,而不仅仅是返回default(WhatEver)
,而只是为了这个成员(VirtualMethod
)。
正如用户BornToCode在评论中注明的那样,如果方法返回类型void
,则无效。当VirtualMethod
无效时,Setup
调用会生成一个Moq.Language.Flow.ISetup<TMock, TResult>
,其继承CallBase()
中的Moq.Language.Flow.IReturns<TMock, TResult>
方法。但是当该方法无效时,我们会得到一个Moq.Language.Flow.ISetup<TMock>
而不是所需的CallBase()
方法。
答案 1 :(得分:8)
由于没有人多久回答这个问题,我认为值得回答,我将集中讨论您提出的最高级问题:如果测试中的方法是虚拟的,如何保持这些好处。
快速回答:你不能用Moq做,或者至少不能直接用。但是,你可以做到。
假设你有两个方面的行为,其中方面A是虚拟的而方面B则不是。这几乎反映了你在课堂上的成就。 B可以使用其他方法吗?这取决于你。
目前,你的班级Foo
正在做两件事 - A和B.我可以说它们是独立的责任,因为你想要模拟A并自己测试B. p>
您可以:
,而不是试图模拟虚拟方法而不嘲笑其他任何东西Foo
到Foo
的构造函数现在你可以模拟A,仍然可以调用B..N的真实代码,而无需实际调用真实的A.你可以保持虚拟或通过接口访问它并模拟它。这也符合单一责任原则。
您可以使用Foo
级联构造函数 - 使构造函数Foo()
调用构造函数Foo(new A())
- 因此您甚至不需要依赖注入框架来执行此操作。
希望这有帮助!
答案 2 :(得分:-1)
是一种方法来调用真正的方法和在方法为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)));
}