简体   繁体   English

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

[英]Using Moq to Mock a class with Unity IoC dependencies

So, we have a custom mock class already that we created manually. 因此,我们已经有一个手动创建的自定义模拟类。 -- it works great for our existing tests, but we also want to be able to verify that given methods were called on it (ie mock.Verify ). -它对我们现有的测试非常mock.Verify ,但是我们也希望能够验证是否在其上调用了给定的方法( mock.Verify )。 -- I could just add a million booleans, one for each method and property in our mock class to verify that each method was called with the correct parameters, but this would get really ugly and hairy, and ultimately wouldn't have the same fidelity as using Moq. -我可以只添加一百万个布尔值,为模拟类中的每个方法和属性添加一个布尔值,以验证是否使用正确的参数调用了每个方法,但这会变得非常丑陋和冗长,最终不会具有相同的保真度。如使用最小起订量。

If the mock class didn't have injected dependencies, I could just create a new one using new Mock<MyMockObject>() , and everything would work fine. 如果模拟类没有注入依赖项,那么我可以使用new Mock<MyMockObject>()创建一个新类,一切都会正常进行。 -- But it also has dependencies injected automatically by Unity, so in this case, it would fail, and leave those properties as null. -但它也具有Unity自动注入的依赖项,因此在这种情况下,它将失败,并将这些属性保留为null。

Is there any way to make the two play nicely together? 有什么办法可以使两者融洽相处吗? -- Any way to tell Moq to use Unity to create the class? -有什么方法可以告诉Moq使用Unity创建类吗? -- Or any way to tell Unity to populate the class created by Moq? -或有什么方法告诉Unity填充Moq创建的类? -- Should I be using a different mocking library that knows about Unity? -我应该使用其他了解Unity的模拟库吗?

Here's a Console Application example of what I'd like to get working; 这是一个我想开始工作的控制台应用程序示例; right now the Bar property comes in as null (because Moq doesn't know about my unity container): 现在, 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
            );
        }
    }
}

EDIT: 编辑:

Guys, please don't get hung up on the interop between Unity and Moq, or on unit testing philosophy. 伙计们,请不要为Unity和Moq之间的互操作或单元测试理念而烦恼。 -- Obviously this code and scenario is very simplified. -显然,此代码和方案非常简化。 -- I'm asking in general on how to make something work. -我一般是在问如何使某项工作正常。

If it helps you to remove Unity from the situation and think of this question as a Moq only question, that's fine, at that point, the question would be: 如果它可以帮助您从情况中消除Unity并将该问题视为仅Moq的问题,那么很好,此时,该问题将是:

If I have a pre-constructed Mock Object (it comes from a factory, or magic fairies, or my uncle gave it to me, it doesn't matter, I have an existing mock object): how do I create a Mock wrapper around that existing object instance to intercept the calls that are made to it? 如果我有一个预先构造的模拟对象(它来自工厂或魔术仙子,或者我叔叔把它交给我,没关系,我有一个现成的模拟对象):如何创建一个模拟包装器现有的对象实例拦截对其进行的调用? (Mainly, I want to intercept in order to verify that certain methods were called with certain parameters, or certain properties were called, etc., via the Verify keyword.) (主要是,我想进行拦截以验证是否通过Verify关键字使用某些参数调用了某些方法,或者调用了某些属性,等等。)

So, instead of new Mock<IFoo>() , I want to do something like Mock.GetWrapper(existingFooInstance) . 因此,我想代替new Mock<IFoo>()来做类似Mock.GetWrapper(existingFooInstance)事情。 -- Is this possible? - 这可能吗? -- It looks like there used to be libraries that did this, but what I've found seems to be unmaintained, and not super useful. -似乎曾经有图书馆这样做,但是我发现的东西似乎没有维护,而且没有超级用处。

so what exactly do you want to test there? 那么您到底要在哪里测试? Your Foobar class? 您的Foobar课程? Then you should mock everything but this class. 然后,您应该嘲笑此类之外的所有内容。 If you want to do verifications on the Bar property, you could mock it like this: 如果要在Bar属性上进行验证,则可以这样模拟:

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

After that, you can check if anything on the barMock was called: 之后,您可以检查barMock上是否有任何调用:

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

On another note, if you do unit test you should isolate your class under test as much as possible, so using Unity to actually create it doesn't really seem like the best idea. 另一方面,如果您进行单元测试,则应尽可能隔离要测试的类,因此使用Unity实际创建它似乎并不是一个最佳主意。 In our environment we usually inject everything into the constructor, which would allow us to create the Foobar Object in the Unit Test without any "Unity Magic". 在我们的环境中,我们通常将所有内容注入构造函数中,这将使我们能够在单元测试中创建Foobar对象,而无需任何“ Unity Magic”。 Maybe you could think about that instead of using the [Dependency] Attribute. 也许您可以考虑使用它而不是使用[Dependency]属性。

First of all, if this is unit testing (unclear from your question), using a DI container such as Unity is overkill - you are only supposed to have 1 real class for any given scenario, and the rest should typically be mocks or stubs that are created with the new keyword. 首先,如果这是单元测试(您的问题不清楚),那么使用像Unity这样的DI容器就太过分了-对于任何给定的场景,您只能拥有一个真实的类,而其余的通常应该是模拟或存根,使用new关键字创建。

Secondly, constructor injection should always be the first choice for injecting dependencies. 其次,构造函数注入应该始终是注入依赖项的首选。 For one, you can ensure they are never null for a given scenario, which simplifies the code that uses the field/property. 首先,您可以确保在给定的情况下它们永远不会为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; }
}

Also, IMO using a container's built-in attributes (such as [Dependency] ) to inject properties is a bad choice because it means that your application is dependent on a specific DI container. 同样,使用容器的内置属性(例如[Dependency] )来注入属性的IMO也是一个不好的选择,因为这意味着您的应用程序依赖于特定的DI容器。 Furthermore, property injection should only be used for optional dependencies (that is, dependencies that are ignored by your application if they are null). 此外,属性注入仅应用于可选依赖项(即,如果依赖项为null,则应用程序将忽略它们)。 So, use constructor injection for required dependencies. 因此,对所需的依赖项使用构造函数注入。 If they are optional, either find a convention-based way to inject the dependency, or use a custom attribute (from your application) to signal injection which you can also use for tests. 如果它们是可选的,则可以找到一种基于约定的方式来注入依赖项,或者使用自定义属性(来自您的应用程序)来发出信号,也可以将其用于测试。 Then you don't need to involve a DI container in testing. 然后,您不需要在测试中涉及DI容器。

I recommend you read the book Dependency Injection in .NET . 我建议您阅读.NET中的依赖注入书。 Clearly, you are doing something wrong if you think your unit tests need a DI container and you believe that using constructor injection is somehow "more difficult" than property injection. 显然,如果您认为单元测试需要一个DI容器并且您认为使用构造函数注入比属性注入“难”得多,那么您做错了。 I can't tell you exactly what is wrong though because your example is oversimplified and doesn't demonstrate why you think you need a container for unit tests. 我无法确切地告诉您什么地方出了问题,因为您的示例过于简化,并且没有说明您为什么认为需要用于单元测试的容器。

Dependency Injection is about making an application loosely-coupled, which usually involves significantly limiting (and refactoring) the application design to make it follow the DI pattern. 依赖注入是关于使应用程序松耦合,这通常涉及显着限制(和重构)应用程序设计以使其遵循DI模式。 The use of a container is completely optional. 容器的使用完全是可选的。 This makes it extremely simple to compose your application with a DI container, and compose unit tests manually. 这使得使用DI容器编写应用程序以及手动编写单元测试变得极其简单。 In fact, putting a DI container in your unit tests only serves to make your tests more difficult to understand. 实际上,将DI容器放入单元测试中只会使测试更加难以理解。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM