hap*_*ore 23 c# unit-testing moq mocking
我已阅读本答案由Ragzitsu了同样的问题.我仍然对如何实现事情感到困惑.有人可以给我一个实现的例子.
我有以下课程:
class Fizz : IFizz
{
}
class Buzz : IBuzz
{
}
class Bar : IBar
{
}
class Foo : IFoo
{
public Foo(IBar bar, IFizz fizz, IBuzz buzz)
{
//initialize etc.
}
//public methods
}
Run Code Online (Sandbox Code Playgroud)
在这里绕过构造函数的实用方法是什么?我想做点什么
var foo = new Mock<IFoo>();
Run Code Online (Sandbox Code Playgroud)
换句话说,代码将如何看待建议
The best thing to do would be right click on your class and choose Extract interface.
Ash*_*lam 29
你可以通过引用MockBehavior来创建mock,其中构造函数有param参数,如下所示
Mock<testClass>(MockBehavior.Strict, new object[] {"Hello"});
Run Code Online (Sandbox Code Playgroud)
Jos*_*ust 10
我知道这是前一段时间被问到的,也许现在对我来说没有价值,但是对于未来的读者来说,让我们不要认为使用moq时以下是相同的事情:
public class MyFooTests
{
... // Tests
private class SubFoo : Foo
{
public override void MethodToAbstract(bool par)
{
// do something expected
}
}
}
Run Code Online (Sandbox Code Playgroud)
是不一样的:
public class Baz
{
private Foo foo;
public Baz(Foo inputFoo)
{
this.foo = inputFoo;
}
public bool GetFromTheFoo()
{
// other stuff here
var fooStuff = this.foo.GetStuff();
// logic on the stuff;
return fooStuff != null;
}
}
Run Code Online (Sandbox Code Playgroud)
我的理解是,在创建一个IFoo模拟时,moq框架将生成一个实现IFoo接口的双类,从而在生成的实现中为自己提供默认构造函数.然而,Foo的模拟不直接在生成的代码中实现接口,而是从具体的Foo类继承,因此需要将任何要存根的方法设为虚拟,如果有任何具体的实现,则需要要实际使用(例如,被测单元),那么你需要在模拟器上使用"CallBase = true".如果您正在寻找具有构造函数参数的具体类,那么您可能正在寻找@Palkin的答案以避免暴露默认构造函数.
作为一个实际回答问题的编辑
代码如何看待建议最好的办法是...提取界面?
提取界面的建议可能并不完全符合您的要求.正如其他答案中提到的那样,我们不确定您实际上是在尝试测试它.
如果你试图在具体 IFoo
实现上测试功能var bazz = new Bazz(new Mock<IFoo>());
然后提取一个接口并且模拟Foo
并不会真正帮助你,因为moq将实现接口本身并且只提供你告诉它提供的"功能"设置该实现的方法和属性.如果这是你正在做的事情,并且测试中的方法在具体Bazz
实现上调用了一些其他方法,并且该方法是你想要提供的抽象,那么我会像@Chris Marisic所提到的那样改变你想要的方法摘要到Foo
.通过虚拟化,您可以使用模拟框架来提供抽象,或者像过去那样完成,并在测试类中创建一个测试对象的子类型.
object GetStuff();
Run Code Online (Sandbox Code Playgroud)
使用此方法,您仍然需要为Foo的构造函数提供一些东西,但这将是在这些接口上使用模拟的主要情况.
现在,如果你正在测试IFoo
和测试方法正在调用方法Foo
,Bazz
,Foo
,有你想要创建没有其他的抽象,那么你就已经建立.模拟这些接口,将它们设置为返回您在特定测试用例中的预期,并提供Foo
模拟对象.
当被测对象依赖于Foo时,提取界面建议非常有用,如@Chris Marisic所述.但是,我可能不会因为不想在这种情况下创建接口而分享他的观点.这取决于你的气味:创建一个提供模拟能力的界面,或改变修饰符以提供相同的功能.如果您要更改修改器并仍然使用moq来模拟具体IFoo
,则需要公开默认构造函数(对我来说很臭)或者使用给定的构造函数参数规范创建模拟,如引用的答案中所述.
假设您有以下内容:
foo.Setup(f => f.GetStuff()).Returns(new object());
Run Code Online (Sandbox Code Playgroud)
在上面IFoo
取决于Foo
.提取界面后virtual
,Foo
需要接受IBar
.
然后IFizz
看起来就像你的问题一样,并且IBuzz
会为你要抽象的方法设置一个签名定义Foo
.
var fooDouble = new Mock<IFoo>();
Run Code Online (Sandbox Code Playgroud)
现在,在您的测试中,您仍然会使用Foo
并提供Baz
给您的Foo
测试科目.不要忘记设置Foo
方法.
var fooDouble = new Mock<Foo>();
Run Code Online (Sandbox Code Playgroud)
因为你现在已经创建了Baz
抽象,所以没有必要
在这里绕过构造函数?
因为模拟IFoo
只有moq提供的默认构造函数.
我个人比较喜欢的接口方法时,它适用,因为它消除Foo
,IFoo
以及Baz
从依赖链var foo = new Mock<IFoo>();
.如果foo
依赖Baz
和IFoo.GetStuff()
依赖IFoo
,那么IFoo
还取决于IFizz
提取后IBuzz
,仅IBar
取决于Baz
.
如果你有一个接口IFoo
并且想要模拟Foo
具有带参数的构造函数的类,则不应该更改任何内容.
您的代码正是您所需要的.
var foo = new Mock<IFoo>();
Run Code Online (Sandbox Code Playgroud)
以下建议涵盖了类没有接口且没有无参数构造函数的情况.实际上你可以将所有需要的参数传递给构造函数Mock
.
var mock = new Mock<IFoo>("constructor", "arguments");
Run Code Online (Sandbox Code Playgroud)
但
"最好的办法是右击你的课程并选择Extract interface." (C)
最好的办法是右键单击您的班级并选择Extract interface.
我将以正交的方式解决这个概念.我不同意这种说法,界面不是解决问题的方法.
回到上一个问题的文字:
public class CustomerSyncEngine {
public CustomerSyncEngine(ILoggingProvider loggingProvider,
ICrmProvider crmProvider,
ICacheProvider cacheProvider) { ... }
public void MethodWithDependencies() {
loggingProvider.Log();
crmProvider.Crm();
cacheProvider.Cache();
}
}
Run Code Online (Sandbox Code Playgroud)
请注意我添加的方法.
我认为,真正的问题是,当你并非专门测试CustomerSyncEngine
,而是正在测试一个类依赖于CustomerSyncEngine
.我们叫这个班SuperSyncEngine
.创建一个测试SuperSyncEngine
将是一个痛苦,因为你必须CustomerSyncEngine
用它的3个接口模拟整个,以及任何其他附加的依赖SuperSyncEngine
.
鉴于你要测试的代码是SuperSyncEngine
依赖于CustomerSyncEngine
接口不是这里的答案.您可以创建,ICustomerSyncEngine
但不应仅为模拟框架创建该接口.更好的解决办法是改变CustomerSyncEngine.MethodWithDependencies
是虚拟
public virtual void MethodWithDependencies() {
loggingProvider.Log();
crmProvider.Crm();
cacheProvider.Cache();
}
Run Code Online (Sandbox Code Playgroud)
这将允许您使用模拟框架替换该方法,忽略CustomerSyncEngine
随附的依赖项.
如果您遵循这种方法,您可能需要暴露一个默认构造函数CustomerSyncEngine
以允许它被模拟.您可以解决这个问题,并使用null或其他值来满足依赖关系,但是当目标是减少摩擦时,这将是额外的工作.
如果你去测试你的 FOo 类,你不需要模拟。您只需要模拟那些依赖于您尝试测试的类的类。
就像是:
Mock<IBar> bar = new Mock<IBar>();
Mock<IBuzz> buzz = new Mock<IBuzz>();
Mock<IFizz> fizz= new Mock<IFizz>();
Foo foo = new Foo(bar.Object, buzz.Object, fizz.Object);
Run Code Online (Sandbox Code Playgroud)
然后调用 foo 中要测试的方法;)
如果 foo 中的方法在 bar/fuzz 或 fizz 中使用了一些方法,那么您应该使用如下语法:
buzz.Setup(x => x.DoSomething()).Returns(1);
Run Code Online (Sandbox Code Playgroud)
这样当你的 foo 方法被调用时,它会调用 DoSomething 并且总是会返回 1 ;)
这是一个很老的帖子,但我遇到了类似的问题(从Moq开始).如果其他人有类似的问题,这就是我所拥有的:
class Bar : IBar
{
}
class Foo : IFoo
{
public Foo(IBar bar)
{
//initialize etc.
}
//public methods
}
class Manager : IManager
{
public Manager(Foo foo)
{
//initialize etc
}
}
Run Code Online (Sandbox Code Playgroud)
我想要做的就是测试Manager
不是Foo
.
这是我的初始测试代码,它引发了一个错误.
[TestFixture]
public class ManagerTest
{
[Test]
public void SomeTest()
{
var fooMock = Mock<IFoo>();
var managerUnderTest = new Manager(fooMock.Object);
}
}
Run Code Online (Sandbox Code Playgroud)
错误是 Castle.DynamicProxy.InvalidProxyConstructorArgumentsException : Can not instantiate proxy of class: Something.Models.Foo. Could not find a parameterless constructor.
阅读错误消息,Moq不了解如何实例化Foo,因为没有无参数构造函数,我们也没有告诉Moq如何使用参数实例化一个.将第二部分更改为:
[TestFixture]
public class ManagerTest
{
[Test]
public void SomeTest()
{
var barMock = Mock<IBar>();
var fooMock = Mock<IFoo>(barMock.Object);
var managerUnderTest = new Manager(fooMock.Object);
//proceed with test
}
}
Run Code Online (Sandbox Code Playgroud)
当一种类型有多个实现时,接口很有用。从消极的一面来看,这意味着您应该注意不要仅仅因为您想要接口而派生接口(常见错误)。从积极的一面来看,模拟接口是定义上的第二种实现。
因此,结论是 - 如果一个类型充当其他类型的依赖项,那么它是实现接口的良好候选者。在这种情况下,您将能够在单元测试中自由且完整地模拟接口。
与此相关的是,在定义接口时,请确保仅向接口添加功能部分:定义对象做什么的方法,而不是定义对象的外观。拥有一个包含大量 getter/setter 的接口并不会增加设计的价值。这是相当大的理论领域,这个小窗口不适合写更多内容。
为了澄清与您的问题的联系:模拟实现应该提供接口所需的行为。为此,您可以使用模拟框架的功能。这与 Foo 类的具体实现无关 - 您定义 Mock 对象的特定行为。