35. Testing

Spring Boot provides a number of useful tools for testing your application. The spring-boot-starter-test POM provides Spring Test, JUnit, Hamcrest and Mockito dependencies. There are also useful test utilities in the core spring-boot module under the org.springframework.boot.test package.

35.1 Test scope dependencies

If you use the spring-boot-starter-test ‘Starter POM’ (in the test scope), you will find the following provided libraries:

  • Spring Test — integration test support for Spring applications.
  • JUnit — The de-facto standard for unit testing Java applications.
  • Hamcrest — A library of matcher objects (also known as constraints or predicates) allowing assertThat style JUnit assertions.
  • Mockito — A Java mocking framework.

These are common libraries that we generally find useful when writing tests. You are free to add additional test dependencies of your own if these don’t suit your needs.

35.2 Testing Spring applications

One of the major advantages of dependency injection is that it should make your code easier to unit test. You can simply instantiate objects using the new operator without even involving Spring. You can also use mock objects instead of real dependencies.

Often you need to move beyond ‘unit testing’ and start ‘integration testing’ (with a Spring ApplicationContext actually involved in the process). It’s useful to be able to perform integration testing without requiring deployment of your application or needing to connect to other infrastructure.

The Spring Framework includes a dedicated test module for just such integration testing. You can declare a dependency directly to org.springframework:spring-test or use the spring-boot-starter-test ‘Starter POM’ to pull it in transitively.

If you have not used the spring-test module before you should start by reading the relevant section of the Spring Framework reference documentation.

35.3 Testing Spring Boot applications

A Spring Boot application is just a Spring ApplicationContext so nothing very special has to be done to test it beyond what you would normally do with a vanilla Spring context. One thing to watch out for though is that the external properties, logging and other features of Spring Boot are only installed in the context by default if you use SpringApplication to create it.

Spring Boot provides a @SpringApplicationConfiguration annotation as an alternative to the standard spring-test @ContextConfiguration annotation. If you use @SpringApplicationConfiguration to configure the ApplicationContext used in your tests, it will be created via SpringApplication and you will get the additional Spring Boot features.

For example:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleDataJpaApplication.class)
public class CityRepositoryIntegrationTests {

    @Autowired
    CityRepository repository;

    // ...

}
[Tip]Tip

The context loader guesses whether you want to test a web application or not (e.g. with MockMVC) by looking for the @WebAppConfiguration annotation. (MockMVC and @WebAppConfiguration are part of spring-test).

If you want a web application to start up and listen on its normal port, so you can test it with HTTP (e.g. using RestTemplate), annotate your test class (or one of its superclasses) with @IntegrationTest. This can be very useful because it means you can test the full stack of your application, but also inject its components into the test class and use them to assert the internal state of the application after an HTTP interaction. For example:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleDataJpaApplication.class)
@WebAppConfiguration
@IntegrationTest
public class CityRepositoryIntegrationTests {

    @Autowired
    CityRepository repository;

    RestTemplate restTemplate = new TestRestTemplate();

    // ... interact with the running server

}
[Note]Note

Spring’s test framework will cache application contexts between tests. Therefore, as long as your tests share the same configuration, the time consuming process of starting and stopping the server will only happen once, regardless of the number of tests that actually run.

To change the port you can add environment properties to @IntegrationTest as colon- or equals-separated name-value pairs, e.g. @IntegrationTest("server.port:9000"). Additionally you can set the server.port and management.port properties to 0 in order to run your integration tests using random ports. For example:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MyApplication.class)
@WebAppConfiguration
@IntegrationTest({"server.port=0", "management.port=0"})
public class SomeIntegrationTests {

    // ...

}

See Section 64.4, “Discover the HTTP port at runtime” for a description of how you can discover the actual port that was allocated for the duration of the tests.

35.3.1 Using Spock to test Spring Boot applications

If you wish to use Spock to test a Spring Boot application you should add a dependency on Spock’s spock-spring module to your application’s build. spock-spring integrates Spring’s test framework into Spock.

Please note that you cannot use the @SpringApplicationConfiguration annotation that was described above as Spock does not find the @ContextConfiguration meta-annotation. To work around this limitation, you should use the @ContextConfiguration annotation directly and configure it to use the Spring Boot specific context loader:

@ContextConfiguration(loader = SpringApplicationContextLoader.class)
class ExampleSpec extends Specification {

    // ...

}
[Note]Note

The annotations described above can be used with Spock, i.e. you can annotate your Specification with @IntegrationTest and @WebAppConfiguration to suit the needs of your tests.

35.4 Test utilities

A few test utility classes are packaged as part of spring-boot that are generally useful when testing your application.

35.4.1 ConfigFileApplicationContextInitializer

ConfigFileApplicationContextInitializer is an ApplicationContextInitializer that can apply to your tests to load Spring Boot application.properties files. You can use this when you don’t need the full features provided by @SpringApplicationConfiguration.

@ContextConfiguration(classes = Config.class,
    initializers = ConfigFileApplicationContextInitializer.class)

35.4.2 EnvironmentTestUtils

EnvironmentTestUtils allows you to quickly add properties to a ConfigurableEnvironment or ConfigurableApplicationContext. Simply call it with key=value strings:

EnvironmentTestUtils.addEnvironment(env, "org=Spring", "name=Boot");

35.4.3 OutputCapture

OutputCapture is a JUnit Rule that you can use to capture System.out and System.err output. Simply declare the capture as a @Rule then use toString() for assertions:

import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.test.OutputCapture;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

public class MyTest {

	@Rule
	public OutputCapture capture = new OutputCapture();

	@Test
	public void testName() throws Exception {
		System.out.println("Hello World!");
		assertThat(capture.toString(), containsString("World"));
	}

}

35.4.4 TestRestTemplate

TestRestTemplate is a convenience subclass of Spring’s RestTemplate that is useful in integration tests. You can get a vanilla template or one that sends Basic HTTP authentication (with a username and password). In either case the template will behave in a test-friendly way: not following redirects (so you can assert the response location), ignoring cookies (so the template is stateless), and not throwing exceptions on server-side errors. It is recommended, but not mandatory, to use Apache HTTP Client (version 4.3.2 or better), and if you have that on your classpath the TestRestTemplate will respond by configuring the client appropriately.

public class MyTest {

	RestTemplate template = new TestRestTemplate();

	@Test
	public void testRequest() throws Exception {
		HttpHeaders headers = template.getForEntity("http://myhost.com", String.class).getHeaders();
		assertThat(headers.getLocation().toString(), containsString("myotherhost"));
	}

}