Your Own Spring Test Context

When applications become big and complex, we are presented with a whole new set of challenges. As engineers, we have to find ways to overcome them. Read how we addressed one of those challenges: Spring integration tests performance.

Spring Framework1 is widely used in Java applications due to the powerful set of features it provides.2 One important feature is the implementation of Inversion of Control (IoC) principle,3 also known as dependency injection (DI): the object itself does not instantiate dependencies, it defines them. Spring is responsible for creating the object and passing in its dependencies. Objects created by Spring are called beans and are managed by Spring application context.4

The startup time for Spring applications increases proportionately with the number of beans. Build time also increases. Execution of tests requiring a Spring application context, commonly called Integration Tests, contribute considerably to that build time: there are more test to execute and it takes increasingly longer to run each one of them.

Here at eBay, to meet requirements such as security, accountability and reliability, Spring applications can increase to a point where integration tests are quite slow. Integration tests have an important role;5 not writing them is out of the question. If they take too long to execute, running them often during development is also out of the question. In extremes cases, integration tests are implemented at the end and are executed only by the CI server.

What if there was a way of speeding up Spring-based integration tests? Build time would much shorter, we could run tests more frequently, and we wouldn't have to wait on the CI server to get feedback. Good news, there is: creating a custom Spring context for integration tests.

Some theory

A Spring context can be configured using an XML-based6 or an annotation-based approach.7 In an annotation-based approach, classes annotated with @Component or objects created inside classes annotated with @Configuration are considered beans and are Spring aware. The scope of beans picked up by Spring can be narrowed down with @ContextConfiguration8 using configurable values (modifiers):

  • classes define classes to include individually

  • location or locations indicate XML configuration file(s) to use

@ComponentScan used with @Configuration can include all beans contained in a entire package structure and is a good alternative to defining classes individually in @ContextConfiguration when a considerable number of beans is involved. The XML tag context:component-scan achieves the same purpose in a XML configuration approach.9 Scanning packages for beans is easier to apply if the code is modular and coupling between packages is low.

Using the mechanisms described above, Spring context can be reduced by creating only the beans required for the integration tests, improving its performance.

Speeding up things

Imagine we want to write a test for a REST10 endpoint of a Spring application. HTTP requests to that specific endpoint should be forwarded by Spring MVC11 to SomeController bean and cause a chain of invocations all the way down to bean Dependency. Dependency bean should replaced with a mock object in this test. We want to be able to send an HTTP request and assert its response. 

Diagram with HTTP request being sent to Spring MVC and causing invocation to SomeController, SomeService and Dependency

Figure 1: Invocation hierarchy to test

The starting point to write this test is to create a @Configuration class where a Dependency mock object is initialized:

@Configuration
public class SomeConfig {

    @Bean
    public Dependency dependency() {
        return mock(Dependency.class);
    }
}

Snippet 1: Configure a mock in Spring context

Next, the SomeController and SomeService and SomeConfig are added to Spring context using @ContextConfiguration and specifying them individually. Notice the mock Dependency created at SomeConfig is being injected in into the test class.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
classes = {
SomeConfig.class,
SomeController.class,
SomeService.class
})
public class SomeControllerPickBeanITTest {

@Autowired
public Dependency dependency;

Snippet 2: Integration test with Spring context configuration

Using Spring MockMvc12 test util, SomeController can be made accessible through HTTP on the test, resembling production execution.

    @Autowired
SomeController someController;

private MockMvc mvc;

@Before
public void setup() {
mvc = MockMvcBuilders.standaloneSetup(someController).build();
}

Snippet 3: Registering Spring REST controller on MockMvc

The test example below sends an HTTP request to the application and asserts if the response contains the expected values. The expected values can be derived from the values Dependency mock was configured to return.

    @Test
public void test() throws Exception {
String id = "id";
String description = "description";
when(dependency.getItemDescription(id)).thenReturn(description);

mvc.perform(
get("/item/" + id).
andExpect(status().isOk()).
andExpect(content().string(containsString(description))));
}

Snippet 4: Test example

This test will have a Spring context of its own, with only the beans we specified. The beans are initialized by Spring and added to its application context.

How long does it takes the test to execute? Well under a second, but you can test it for yourself. The whole source code can be found at this Github repository. The same source code contains an equivalent example for JAX-RS.13,14

References

1Spring Homepage

2Spring Core Technologies

3Inversion of Control - IoC

4Understanding Application Context

5 Integration Testing

6Spring XML-based configuration

7Spring Context Configuration example

8Spring Context Configuration JavaDoc

9Using Spring XML component-scan Tag example

10Representation state transfer (REST)

11Spring Web MVC

12Spring MVC Test Integration

13JAX-RS Java EE 6 tutorial

14Integration tests using JAX-RS example