Dependency Injection

Dependency injection1 has grown from a hand-spun and somewhat obscure application assembly approach to a mainstream and widespread approach. Implemented in the Spring framework (Listing 12-1) and the EJB 3.0 (Listing 12-2) specifications in the Java world, Windsor container in .NET,2 and others in many languages, use of dependency injection and inversion of control frameworks has become commonplace, if not always well understood. The prevalence of the frameworks and the principles behind them yields a high likelihood that we can use this pattern for our testing.

1. Also known as Inversion of Control. See www.martinfowler.com/articles/injection.html for an early unifying treatment on the topic.

2. http://docs.castleproject.org/Windsor.MainPage.ashx

There are three widely accepted forms of dependency injection detailed by Martin Fowler and several less well-known forms, such as those available in the Yan framework.3 Constructor and setter injection (both shown in Listing 12-3) prevail in the mainstream Java frameworks, and both are well supported.

3. http://yan.codehaus.org/Dependency+Injection+Types

Listing 12-1: Spring framework configuration of a service dependent on two other services. One service is injected using constructor injection, the other using setter injection. Note that “Image” indicates an artificial line break for purposes of formatting.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation=
         "http://www.springframework.org/schema/beans Image
         http://www.springframework.org/schema/beans/Image
           spring-beans-2.0.xsd">
  <bean id="userServiceBean" class="..."/>
  <bean id="productServiceBean" class="..."/>
  <bean id="shoppingCartService" class="...">
    <constructor-arg ref="userServiceBean"/>
    <property name="productService" ref="productServiceBean"/>
  </bean>
</beans>

Listing 12-2: Equivalent configuration to Listing 12-1 using JSR 330 annotations

public class ShoppingCartServiceImpl
    implements ShoppingCartService {
  private UserService userService;
  @Inject
  private ProductService productService;

  @Inject
  public ShoppingCartServiceImpl(UserService userService) {
    this.userService = userService;
  }
}

Listing 12-3: Testing a method on the class in Listing 12-2 directly using EasyMock to mock the collaborators

import static org.easymock.EasyMock;

public class ShoppingCartServiceImplTest {
  @Test
  public void createCartWithProduct() {
    UserService userService =
      EasyMock.createMock(UserService.class);
    ProductService productService =
      EasyMock.createMock(ProductService.class);
    // Configure mocks
    EasyMock.replay(userService, productService);
    ShoppingCartService sut =
      new ShoppingCartServiceImpl(userService);
    sut.setProductService(productService);
    // Execute and assert something about the cart
  }
}

Constructor injection allows dependencies to be passed as parameters to a constructor. With this form, you declare all of the dependencies for an object in one place, but circular dependencies are not supported.

Setter injection relies on default constructors, and the object has at least a conventional setter to inject each dependency. Instances are created and injected as separate steps, permitting dependencies to be more flexibly resolved by the framework. With languages that support reflection, setter methods are not strictly required, although omitting them requires dependency injection support in your tests, as shown in Listing 12-4.

Listing 12-4: Spring framework support for file-based configuration helps you inject alternate implementations when testing.

package com.vance.qualitycode;

@RunWith(SpringJUnit4ClassRunner.class)
@Configuration({"test-context.xml"})
public class ContextTest {
...
}

The two forms of injection can be mixed in the same initialization, but the important feature for our purposes is that there are explicit parameter lists for us to use to inject collaborators for our tests. The forms that allow dependency injection also allow us to directly test the objects. Mocks are often used for the injected components to simplify the test double creation process.

The frameworks also provide facilities to inject collaborators using the injection infrastructure to simplify the construction of objects with many dependencies. In particular, the Spring framework supports this well.4 It supports loading application contexts from files as in Listing 12-4 and, as of version 3.1, supports configuration classes (Listing 12-5). Both of these permit the injection of alternate implementations in the testing context.

4. http://static.springsource.org/spring/docs/3.1.0.M2/spring-framework-reference/html/testing.html#testcontext-framework

Listing 12-5: As of version 3.1, the Spring framework supports configuration classes, allowing injected instances to be defined in the test code itself.5

package com.example;

@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext will be loaded from the static inner
// ContextConfiguration class
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class)
public class OrderServiceTest {

  @Configuration
  static class ContextConfiguration {

    // This bean will be injected into
    // the OrderServiceTest class
    @Bean
    public OrderService orderService() {
      OrderService orderService = new OrderServiceImpl();
      // Set properties, etc.
      return orderService;
    }
  }

  @Autowired
  private OrderService orderService;

  @Test
  public void testOrderService() {
    // Test the orderService
  }
}

5. This example was taken from http://www.swiftmind.com/de/2011/06/22/spring-3-1-m2-testing-with-configuration-classes-and-profiles/ with the permission of Sam Brannen.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.51.145