Dependency Injection: Turtles all the way down?

后端 未结 5 839
执笔经年
执笔经年 2020-12-01 18:11

So I\'m wondering about how unit testing works in regards to dealing external dependencies. Here and elsewhere I\'ve become familiar with dependency injection, and how that

相关标签:
5条回答
  • 2020-12-01 18:52

    In order to use Dependency Injection, your classes would have their dependencies injected into them (several ways to do that - constructor injection, property injection) and would not instantiate them themselves, as you do in your examples.

    Additionally, one would extract the interface of each dependency to help with testability and use the interface instead of an implementation type as the dependency.

    class Foo
    {
        private IExternalDependency ed;
        public int doSomethingWithExternalDependency() {...}
    
        public Foo(IExternalDependency extdep)
        {
          ed = extdep;
        }
    }
    

    What most people do is use a mocking framework to mock the dependencies when testing.

    You can mock any object that the class under test depends on (including behavior and return values) - pass the mocks to the class as its dependencies.

    This allows you to test the class without relying on the behavior of its (implemented) dependencies.

    In some cases, you may want to use fakes or stubs instead of a mocking framework. See this article by Martin Fowler about the differences.


    As for getting all the dependencies, all the way down - one uses an IoC container. This is a registry of all of the dependencies in your system and understands how to instantiate each and every class with its dependencies.

    0 讨论(0)
  • 2020-12-01 18:56

    When you unit test a class, you should mock its dependencies, to test your class in isolation -- this is regardless of dependency injection.

    The answer to your question about Bar is: yes, you should inject Foo. Once you go down the DI path, you will use it across your entire stack. If you really need a new Foo for every doSomethingWithFoo call, you might want to inject a FooFactory (which you can then mock for testing purposes), if you want a single Bar to use many Foos.

    0 讨论(0)
  • 2020-12-01 19:05

    The examples you provide do not use Dependency Injection. Instead, Bar should use Constructor Injection to get a Foo instance, but there's no point in injecting a concrete class. Instead, you should extract an interface from Foo (let's call it IFoo) and inject that into Bar:

    public class Bar
    {
        private IFoo f;
    
        public Bar(IFoo f)
        {
            this.f = f;
        }
    
        public int doSomethingWithFoo
        {
            int x = this.f.doSomethingWithExternalDependency();
            // Do some more stuff ...
            return result;
        }
    }
    

    This enables you to always decouple consumers and dependencies.

    Yes, there will still be a place where you must compose the entire application's object graph. We call this place the Composition Root. It's a application infrastructure component, so you don't need to unit test it.

    In most cases you should consider using a DI Container for that part, and then apply the Register Resolve Release pattern.

    0 讨论(0)
  • 2020-12-01 19:08

    Keep in mind the difference between unit testing and integration testing. In the former, the dependency would be mocked whereby it provides expected behavior for the purpose of testing the class which consumes the dependency. In the latter, an actual instance of the dependency is initialized to see if the whole thing works end-to-end.

    0 讨论(0)
  • 2020-12-01 19:15

    I'd like to stress out that in case of unit testing you should have two separate sets of tests: one for Foo.doSomethingWithExternalDependency and another one for Bar.doSomethingWithFoo. In the latter set create mock implementaion of Foo and you test just doSomethingWithFoo assuming that doSomethingWithExternalDependency works properly. You test doSomethingWithExternalDependency in a separate test set.

    0 讨论(0)
提交回复
热议问题