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
<?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
http://www.springframework.org/schema/beans/
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>
public class ShoppingCartServiceImpl
implements ShoppingCartService {
private UserService userService;
@Inject
private ProductService productService;
@Inject
public ShoppingCartServiceImpl(UserService userService) {
this.userService = userService;
}
}
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.
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
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.
3.137.186.178