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

时间:2013-12-22 09:03:37

标签: c# unit-testing mocking moq

我已经阅读了Ragzitsu关于同一问题的answer。我仍然对如何实现事情感到困惑。有人可以给我一个实现的例子。

我有以下课程:

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.

7 个答案:

答案 0 :(得分:23)

你可以通过引用MockBehavior来创建mock,其中构造函数有param参数,如下所示

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

答案 1 :(得分:8)

我知道这是在不久前被问到的,也许现在对我来说没有价值,但是对于未来的读者来说,让我们不要认为以下是同样的事情使用moq时:

var fooDouble = new Mock<IFoo>();

与:

不同
var fooDouble = new Mock<Foo>();

我的理解是,在创建一个IFoo模型时,moq框架将生成一个实现IFoo接口的双类,从而在生成的实现中为自己提供默认构造函数。然而,Foo的模拟不直接在生成的代码中实现接口,而是从具体的Foo类继承,因此需要将任何要存根的方法设为虚拟,如果有任何具体的实现,则需要实际使用(例如,被测单位)然后你需要&#34; CallBase = true&#34;在你的模拟。如果您正在寻找具有构造函数参数的具体类,那么您可能正在寻找@Palkin的答案以避免暴露默认构造函数。

作为实际回答

问题的编辑
  

代码如何看待建议最好的办法是......提取界面?

提取界面的建议可能不是您正在寻找的。正如在其他答案中提到的那样,我们不确定您实际上是在尝试测试

如果您尝试在Foo的具体 IFoo实现上测试功能,那么提取界面并模拟IFoo并不是真的要去为了帮助你,因为moq将实现界面本身并且仅提供&#34;功能&#34;你告诉它提供该实现的设置方法和属性。如果您正在进行此操作,那么 AND 正在测试的方法会在具体的Foo实施中调用其他方法, AND 该方法是您要提供的方法这是一个抽象,然后我会像@Chris Marisic一样提到并改变你希望抽象到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,则需要公开默认构造函数(非常臭我)或创建使用给定的构造函数参数规范进行模拟,如referenced answer中所述。

假设您有以下内容:

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>();并将foo提供给您的Baz测试主题。不要忘记设置IFoo.GetStuff()方法。

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

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

  

在这里绕过构造函数?

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

我个人更喜欢使用界面方法,因为它会从IFizz上的依赖关系链中删除IBuzzIBarBaz。如果Baz取决于FooFoo取决于IFizz etc.,那么Baz也取决于IFizz etc.解压后IFoo,{ {1}}仅取决于Baz

答案 2 :(得分:7)

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

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

var foo = new Mock<IFoo>();

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

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

  

“最好的办法是右击你的课程并选择Extract interface。” (c)中

答案 3 :(得分:7)

  

最好的办法是右键单击您的课程并选择Extract interface。

我将以正交的方式解决这个概念。我不同意这个陈述,界面不是解决问题的解决方案

回到上一个问题的文字:

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个接口模拟整个CustomerSyncEngine,以及SuperSyncEngine所具有的任何其他依赖项。

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

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

这将允许您使用模拟框架替换该方法,忽略CustomerSyncEngine附带的依赖项。

如果您遵循这种方法,您可能需要在CustomerSyncEngine上公开默认构造函数以允许它被模拟。您可以解决这个问题,并使用null或其他值来满足依赖关系,但是当目标是减少摩擦时,这将是额外的工作。

答案 4 :(得分:5)

这是一个很老的帖子,但我遇到了类似的问题(从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
    }
}

答案 5 :(得分:3)

如果你去测试你的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;)

答案 6 :(得分:0)

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

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

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

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