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

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依赖BazIFoo.GetStuff()依赖IFoo,那么IFoo还取决于IFizz提取后IBuzz,仅IBar取决于Baz.


Ily*_*kin 8

如果你有一个接口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)


Chr*_*sic 7

最好的办法是右键单击您的班级并选择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或其他值来满足依赖关系,但是当目标是减少摩擦时,这将是额外的工作.


Osc*_*alo 5

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


the*_*mbo 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
    }
}
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)


Zor*_*vat 0

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

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

与此相关的是,在定义接口时,请确保仅向接口添加功能部分:定义对象什么的方法,而不是定义对象的外观。拥有一个包含大量 getter/setter 的接口并不会增加设计的价值。这是相当大的理论领域,这个小窗口不适合写更多内容。

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