tag:blogger.com,1999:blog-6959582839690958035.post7879863674010970350..comments2023-09-15T11:15:54.222-04:00Comments on public void Life(): Dependency Injection and Leaky Abstractionstesthttp://www.blogger.com/profile/09609860522747123959noreply@blogger.comBlogger4125tag:blogger.com,1999:blog-6959582839690958035.post-20529752080155539262013-01-31T12:02:16.711-05:002013-01-31T12:02:16.711-05:00Why isn't a test suite an application instance...Why isn't a test suite an application instance? What is a test runner if not an application? Something is running the tests, executing the code. That something is, for all intents and purposes, an application. Capable of its own configuration like any other. Maybe that configuration is a config file, maybe it's a bootstrap method in the test suite, that part is immaterial.<br /><br />By advertising in the constructor what the dependencies are, the object is still advertising implementation details. The test suite can bootstrap the overall domain dependency graph (with mocks) without having to know or care which dependencies each object individually needs. The concern of wiring up the dependencies would belong squarely within the service locator implementation, all an application instance (or test suite) has to do is initialize it on startup. (For an application it would generally happen once, for a test suite it would happen before each isolated test.)<br /><br />Those advertised dependencies in the constructor would put the tests in a position that they need to be changed any time the internal implementation is changed, even if the external behavior is not changed. (A private refactoring.) If changing the internal implementation of a class forces other code outside that class to change, it's tightly coupled.testhttps://www.blogger.com/profile/09609860522747123959noreply@blogger.comtag:blogger.com,1999:blog-6959582839690958035.post-10754300255689609802013-01-31T11:35:02.094-05:002013-01-31T11:35:02.094-05:00Everything you're describing can be accomplish...Everything you're describing can be accomplished using constructor/setter injection, without the side-effect of requiring the unit tests to be aware of the service locator. No matter how you slice it, if the module/method you're testng uses a dependency, your tests are going to have to supply it. I'd argue that constructor injection is the best way for modules to express what they depend on. I'd also argue that a *unit* test suite is *not* another application instance - we should be able to create unit test methods that stand up in complete isolation. For integration testing, sure, it makes sense to have a bootstrapper and test interdependencies.<br /><br />Going back to your original point, you're saying the guy was having trouble because his test-targets had extra dependencies. My point is that moving the dependency to an internal servicelocator call doesn't remove the dependency, it just makes it harder to discover why things aren't working when somehing breaks. In your final example, you're saying tests don't need to know anything about the implementation destails of MyMathObject.Add, but they do - if they don't wire up the widgetdependency, the test will fail.Aidan Ryanhttps://www.blogger.com/profile/10310867577809754531noreply@blogger.comtag:blogger.com,1999:blog-6959582839690958035.post-33911667553361737222013-01-30T18:03:14.541-05:002013-01-30T18:03:14.541-05:00There's an application-level bootstrap that ta...There's an application-level bootstrap that takes place, yes. I imagine this is to be expected, since a test suite is nothing more than another application instance.<br /><br />I can take this a step further and set up my dependency injection container to be configuration-driven as to which dependencies it uses (mocks, production, some other test version such as an XML-based repository instead of a SQL-based one, etc.). That would allow the same static service locator object to be used by the same domain objects in the same context at all times. The application's config file is what points to the mocks or not.<br /><br />So from the perspective of the domain objects, the tests, and everything else there is no difference in any of the configurations. Unit tests become re-usable as integration tests for all available integrations (all dependency implementations).<br /><br />Remember that the service locator object is really just a static-passthru to a dependency itself... The IoC container. That container is also switched out by the application instance at will. It's trivial to turn that static-passthru object dependency into a factory method call to allow more inversion:<br /><br />var dependency = ServiceLocatorFactory.Current.Resolve();testhttps://www.blogger.com/profile/09609860522747123959noreply@blogger.comtag:blogger.com,1999:blog-6959582839690958035.post-74622727333894635522013-01-30T17:47:31.745-05:002013-01-30T17:47:31.745-05:00Won't you have to change the test code regardl...Won't you have to change the test code regardless of the DI method being used? The symptoms will just change - with constructor injection, if a new dependency is added, the unit test will fail to compile; with setter and common service locator, you'll likely hit a NullReferenceException at runtime. So with constructor/setter you have to add a new mock, and with Common Service Locator you have to wire up the new dependency in your unit test bootstrap code.<br /><br />I'd argue that the your preferred option is *less* testable because it has a concrete dependency on the static ServiceLocatorObject. Using constructor or setter injection just requires a mock of the dependency. The coupling has not been eliminated, in fact it's greater - MyMathObject is coupled to ServiceLocatorObject *and* widgetdependency, as opposed to just widgetdependency.Aidan Ryanhttps://www.blogger.com/profile/10310867577809754531noreply@blogger.com