当构造函数具有参数时,使用 Moq 实现模拟对象



我已经阅读了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
}

在这里绕过构造函数的实用方法是什么?我想做类似的事情

var foo = new Mock<IFoo>();

换句话说,代码将如何照顾建议

The best thing to do would be right click on your class and choose Extract interface.

你可以通过引用 MockBehavior 来创建构造函数具有参数参数的模拟,如下所示

Mock<testClass>(MockBehavior.Strict, new object[] {"Hello"}); 

如果你有一个接口IFoo并且想要模拟Foo具有带参数构造函数的类,则不应更改任何内容。

您的代码正是您所需要的。

var foo = new Mock<IFoo>();

以下建议涵盖了类没有接口和无参数构造函数的情况。实际上,您可以将所有需要的参数传递给Mock的构造函数。

var mock = new Mock<IFoo>("constructor", "arguments");

"最好的办法是右键单击您的类并选择提取界面。"(三)

TL;博士

目前还不清楚你到底想做什么。

如果您已经定义了接口IFoo(这就是您的问题似乎显示的内容),并且您需要将其模拟为另一种类型,那么您已经设置了:var bazz = new Bazz(new Mock<IFoo>());

如果您有一个具体的类Foo,尚未将其抽象为需要作为依赖项提供给另一种类型的接口,则提取接口是允许初始化Bazz对象而无需创建实际Foo类的更常用方法之一。 根据实际代码的外观, 您可以通过将Bazz所依赖的Foo部分抽象为IFoo来限制范围。另一种方法是模拟混凝土并为 moq 提供要使用的构造函数参数。您需要确保Foo的依赖项部分是"可模拟的"(公共虚拟),以便为模拟提供您的设置和结果。

阅读更多:

回答以下问题

代码将如何照顾建议最好的办法是...提取接口?

提取接口的建议可能不完全是您要找的。正如在其他答案中提到的,我们不确定您实际上要测试的是什么

如果您尝试在IFoo的具体Foo实现上测试功能,那么提取接口并模拟IFoo并不能真正帮助您,因为 moq 将实现接口本身,并且仅提供您告诉它在该实现的设置方法和属性中提供的"功能"。如果这就是您正在做的事情,并且正在测试的方法在具体的Foo实现上调用其他方法,并且该方法是您想要为其提供抽象的东西,那么我会按照Marisic提到的@Chris并将您希望抽象的方法更改为virtual。有了这个虚拟的,你可以使用一个模拟框架来提供抽象,或者像我过去所做的那样,在测试类中创建一个被测主题的子类型。

public class MyFooTests
{
... // Tests
private class SubFoo : Foo
{
public override void MethodToAbstract(bool par)
{
// do something expected
}
}
}

使用这种方法,您仍然需要向 Foo 的构造函数提供一些东西,但这将是在这些接口上使用模拟的主要情况。

现在,如果您正在测试Foo并且所测试的方法只是在IBarIFizzIBuzz上调用方法,并且没有要创建的其他抽象,那么您已经设置好了。模拟这些接口,将它们设置为返回特定测试用例中期望的内容,并为Foo提供模拟对象。

正如@Chris Marisic 所述,当您的测试对象依赖于 Foo 时,提取接口建议非常有用。但是,我可能不会同意他不想在这种情况下创建接口的观点。这取决于什么对你来说更闻起来:创建一个界面来提供模拟能力,或者更改修饰符以提供相同的功能。如果您要更改修饰符并且仍然使用 moq 来模拟具体的Foo,您将需要公开一个默认构造函数(对我来说很臭)或使用给定的构造函数参数规范创建模拟,如您引用的答案中所述。

假设您有以下内容:

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;
}
}

在上述Baz取决于Foo。提取Foo的接口后,Baz需要接受IFoo

然后Foo看起来像它在你的问题中一样,IFoo会有一个签名定义,用于你想要从Baz中抽象出来的方法。

object GetStuff();

现在,在您的测试中,您仍然会使用var foo = new Mock<IFoo>();并为您的Baz测试对象提供foo。不要忘记设置IFoo.GetStuff()方法。

foo.Setup(f => f.GetStuff()).Returns(new object());

因为您现在已经创建了IFoo抽象,所以不需要

在这里绕过构造函数?

因为IFoo的模拟只有 MOQ 提供的默认构造函数。

切线

我个人更喜欢接口方法,当它适用时,因为它在测试时消除了间接依赖关系。假设您有一个依赖于Foo(非抽象)的类Baz,但现在Foo依赖于其构造函数中的Fizz(或IFizz如您的问题)。

class Fizz 
{
}
class Foo // not abstracted : IFoo
{
private Fizz fizz;
public Foo(Fizz fizz)
{
this.fizz = fizz;
}
public object GetStuff()
{
return this.fizz;
}
}
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;
}
}

测试Baz没有BazFoo之间的抽象,需要模拟、存根或创建一个实际的Fizz,以获得我们可以提供给被测试对象的可测试Foo实例Baz

public void TestBaz()
{
var foo = new Foo(new Fizz());
var baz = new baz(foo);
var isFromBaz = baz.GetFromTheFoo();
}

提供可控Fizz可能并不总是那么容易,并且可能会使您的测试爆炸式增长,其复杂性可能与Baz完全无关。但是,如果我们在FooBaz之间有一个IFoo,那么间接依赖Fizz就会被删除,因为 ctor 是一个实现细节,就像IFoo.GetStuff的实际功能一样。

class Baz
{
private IFoo foo;
public Baz(IFoo inputFoo)
{
this.foo = inputFoo;
}
public bool GetFromTheFoo()
{
// other stuff here
var fooStuff = this.foo.GetStuff();
// logic on the stuff;
return fooStuff != null;
}
}

然后 out 测试可以只是:

public void TestBaz()
{
var foo = Mock.Of<IFoo>();
var baz = new baz(foo);
var isFromBaz = baz.GetFromTheFoo();
}

其中一些取决于您如何定义应包含在任何给定测试中的单位。我尽力使非私有方法成为需要测试的单元的定义,如果我可以在一个非私有调用另一个非私有方法之间画一条抽象的依赖关系线,我就会这样做。

最好的办法是右键单击您的类并选择提取界面。

我将从正交的意义上讨论这个概念。我不同意这种说法,接口不是所讨论的坐姿的解决方案。

回到上一个问题的文本:

public class CustomerSyncEngine {
public CustomerSyncEngine(ILoggingProvider loggingProvider, 
ICrmProvider crmProvider, 
ICacheProvider cacheProvider) { ... }
public void MethodWithDependencies() {
loggingProvider.Log();
crmProvider.Crm();
cacheProvider.Cache();
}
}

请注意我添加的方法。

我相信真正的问题是,当您不是专门测试CustomerSyncEngine而是测试依赖于CustomerSyncEngine的类时。我们称这个类为SuperSyncEngine。针对SuperSyncEngine创建测试将很痛苦,因为您必须使用其 3 个接口以及SuperSyncEngine具有的任何其他附加依赖项来模拟整个CustomerSyncEngine

鉴于您要测试的代码是SuperSyncEngine这取决于CustomerSyncEngine接口不是这里的答案。您可以创建ICustomerSyncEngine但该接口不应仅针对模拟框架创建。更好的解决方案是将CustomerSyncEngine.MethodWithDependencies更改为虚拟

public virtual void MethodWithDependencies() {
loggingProvider.Log();
crmProvider.Crm();
cacheProvider.Cache();
}

这将允许您将该方法替换为忽略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);

然后在 foo 中调用要测试的方法;)

如果 foo 中的方法在 bar/fuzz 或 fizz 中使用某种方法,那么您应该使用 sintax,例如:

buzz.Setup(x => x.DoSomething()).Returns(1);

这样,当您的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
}
}

我想做的是测试Manager而不是Foo

这是我抛出错误的初始测试代码。

[TestFixture]
public class ManagerTest
{
[Test]
public void SomeTest()
{
var fooMock = Mock<IFoo>();
var managerUnderTest = new Manager(fooMock.Object);
}
}

错误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
}
}

当一种类型具有多个实现时,接口很有用。不利的一面是,这意味着您应该注意不要仅仅因为您想要接口而派生接口(常见错误)。从积极的方面来说,模拟接口是定义的第二个实现。

所以,结论是 - 如果一个类型充当其他类型的依赖项,那么它是实现接口的一个很好的候选者。在这种情况下,您将能够自由而完全地在单元测试中模拟界面。

在相关的说明中,在定义接口时,请确保只向接口添加功能部件:定义对象功能而不是外观的方法。与一堆 getter/setter 有一个接口不会为设计增加价值。这是一个相当大的理论领域,这个小窗口不是写更多关于它的地方。

为了澄清与您的问题的联系:模拟实现应提供接口所需的行为。为此,您可以使用模拟框架的功能。这与 Foo 类的具体实现无关 - 您定义了 Mock 对象的特定行为。

最新更新