使用Moq模拟具有Unity IoC依赖关系的类

时间:2017-02-24 22:00:50

标签: c# unit-testing dependency-injection moq unity-container

因此,我们已经手动创建了一个自定义模拟类。 - 它适用于我们现有的测试,但我们也希望能够验证是否在其上调用了给定的方法(即mock.Verify)。 - 我可以在我们的模拟类中为每个方法和属性添加一百个布尔值,以验证每个方法是否使用正确的参数调用,但这会变得非常丑陋和毛茸茸,最终不会有与使用Moq一样保真。

如果模拟类没有注入依赖项,我可以使用new Mock<MyMockObject>()创建一个新的,并且一切都可以正常工作。 - 但它也有Unity自动注入的依赖项,因此在这种情况下,它会失败,并将这些属性保留为null。

有没有什么方法可以让两人一起玩得很好? - 告诉Moq使用Unity创建课程的任何方法? - 或者告诉Unity填充Moq创建的类的任何方式? - 我应该使用一个了解Unity的不同模拟库吗?

这是我想要开始工作的控制台应用示例;现在Bar属性为null(因为Moq不知道我的统一容器):

public class Bar: IBar
{
    public string Text { get; set; }
}

public class Foobar: IFoobar
{
    public virtual string Foo { get; set; }

    [Dependency]
    public virtual IBar Bar { get; protected set; }
}

public class Program
{
    public static void Main(string[] args)
    {
        using (var uc = new UnityContainer())
        {
            // Injected by specific unit test:
            uc.RegisterInstance<IBar>(new Bar() { Text = "Hello World!" });

            // This way works, but I can't verify if methods get called.
            Console.WriteLine
            (
                uc.Resolve<Foobar>().Bar.Text
            );

            // This way I can verify if certain methods were called, but "Bar" will never be
            // populated, so the following will throw a NullReferenceException.
            Console.WriteLine
            (
                new Mock<Foobar>().Object.Bar.Text
            );
        }
    }
}

修改

伙计们,请不要挂断Unity和Moq之间的互操作,或者单元测试理念。 - 显然这个代码和场景非常简单。 - 我一般都在询问如何让事情发挥作用。

如果它可以帮助你从这种情况中删除Unity并将这个问题视为Moq唯一的问题,那很好,那时问题是:

如果我有一个预先构建的模拟对象(它来自工厂,或魔法仙女,或者我的叔叔给了我,它没关系,我有一个现有的模拟对象):我该怎么办在现有对象实例周围创建一个Mock包装器来拦截对其进行的调用? (主要是,我想通过Verify关键字来验证是否使用某些参数调用了某些方法,或者调用了某些属性等等。)

因此,我想做new Mock<IFoo>()之类的事情,而不是Mock.GetWrapper(existingFooInstance)。 - 这可能吗? - 看起来曾经有过这样做的图书馆,但我所发现的似乎没有维护,也没有超级实用。

2 个答案:

答案 0 :(得分:0)

那么你到底要测试的是什么?你的Foobar课程?然后你应该模仿这个类的一切。 如果你想在Bar属性上进行验证,你可以这样模仿它:

var barMock = new Mock<IBar>();
barMock.SetupGet(x => x.Text).Returns("Hello World");
uc.RegisterInstance<IBar>(barMock.Object);

之后,您可以检查barMock上是否有任何内容被调用:

barMock.VerifyGet(x => x.Text);

另一方面,如果你进行单元测试,你应该尽可能地隔离你的测试类,所以使用Unity实际创建它并不是最好的想法。在我们的环境中,我们通常会将所有内容注入到构造函数中,这样我们就可以在单元测试中创建Foobar对象而不需要任何“Unity Magic”。也许你可以考虑一下而不是使用[Dependency]属性。

答案 1 :(得分:0)

首先,如果这是单元测试(从您的问题中不清楚),使用像Unity这样的DI容器是过度的 - 你应该只有1 真实的任何给定方案的类,其余通常应该是使用new关键字创建的模拟或存根。

其次,构造函数注入应始终是注入依赖项的首选。例如,对于给定方案,您可以确保它们永远不会null,这简化了使用字段/属性的代码。

public class Foobar: IFoobar
{
    public FooBar(IBar bar)
    {
        // using a guard clause ensures your dependency can
        // never be null.
        if (bar == null)
            throw new ArgumentNullException("bar");
        this.Bar = bar;
    }

    public virtual string Foo { get; set; }

    public virtual IBar Bar { get; private set; }
}

此外,IMO使用容器的内置属性(例如[Dependency])来注入属性是一个糟糕的选择,因为这意味着您的应用程序依赖于特定的DI容器。此外,属性注入仅应用于可选依赖项(即,如果应用程序为null,则应用程序将忽略这些依赖项)。因此,使用构造函数注入来获取所需的依赖项。如果它们是可选的,要么找到基于约定的注入依赖关系的方法,要么使用自定义属性(来自您的应用程序)来发出注入信号,您也可以将其用于测试。然后,您不需要让DI容器参与测试。

  

我建议你阅读这本书Dependency Injection in .NET。显然,如果你认为你的单元测试需要一个DI容器并且你认为使用构造函数注入在某种程度上更加困难,那么你做错了什么。财产注入。我不能告诉你到底出了什么问题,因为你的例子过于简单,并且没有证明为什么你认为你需要一个容器进行单元测试。

     

依赖注入是指使应用程序松散耦合,这通常涉及显着限制(和重构)应用程序设计,使其遵循DI模式。容器的使用是完全可选的。这样可以非常简单地将应用程序与DI容器组合在一起,并手动组合单元测试。实际上,将DI容器放入单元测试只会使您的测试更难以理解。