40.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 @SpringBootTest
annotation which can be used as an alternative to the standard spring-test
@ContextConfiguration
annotation when you need Spring Boot features. The annotation works by creating the ApplicationContext
used in your tests via SpringApplication
.
You can use the webEnvironment
attribute of @SpringBootTest
to further refine how your tests will run:
MOCK
— Loads aWebApplicationContext
and provides a mock servlet environment. Embedded servlet containers are not started when using this annotation. If servlet APIs are not on your classpath this mode will transparently fallback to creating a regular non-webApplicationContext
.RANDOM_PORT
— Loads anEmbeddedWebApplicationContext
and provides a real servlet environment. Embedded servlet containers are started and listening on a random port.DEFINED_PORT
— Loads anEmbeddedWebApplicationContext
and provides a real servlet environment. Embedded servlet containers are started and listening on a defined port (i.e from yourapplication.properties
or on the default port8080
).NONE
— Loads anApplicationContext
usingSpringApplication
but does not provide any servlet environment (mock or otherwise).
Note | |
---|---|
In addition to @SpringBootTest a number of other annotations are also provided for testing more specific slices of an application. See below for details. |
Tip | |
---|---|
Don’t forget to also add @RunWith(SpringRunner.class) to your test, otherwise the annotations will be ignored. |
40.3.1 Detecting test configuration
If you’re familiar with the Spring Test Framework, you may be used to using @ContextConfiguration(classes=…)
in order to specify which Spring @Configuration
to load. Alternatively, you might have often used nested @Configuration
classes within your test.
When testing Spring Boot applications this is often not required. Spring Boot’s @*Test
annotations will search for your primary configuration automatically whenever you don’t explicitly define one.
The search algorithm works up from the package that contains the test until it finds a @SpringBootApplication
or @SpringBootConfiguration
annotated class. As long as you’ve structured your code in a sensible way your main configuration is usually found.
If you want to customize the primary configuration, you can use a nested @TestConfiguration
class. Unlike a nested @Configuration
class which would be used instead of a your application’s primary configuration, a nested @TestConfiguration
class will be used in addition to your application’s primary configuration.
Note | |
---|---|
Spring’s test framework will cache application contexts between tests. Therefore, as long as your tests share the same configuration (no matter how it’s discovered), the potentially time consuming process of loading the context will only happen once. |
40.3.2 Excluding test configuration
If your application uses component scanning, for example if you use @SpringBootApplication
or @ComponentScan
, you may find components or configurations created only for specific tests accidentally get picked up everywhere.
To help prevent this, Spring Boot provides @TestComponent
and @TestConfiguration
annotations that can be used on classes in src/test/java
to indicate that they should not be picked up by scanning.
Note | |
---|---|
@TestComponent and @TestConfiguration are only needed on top level classes. If you define @Configuration or @Component as inner-classes within a test (any class that has @Test methods or @RunWith ), they will be automatically filtered. |
Note | |
---|---|
If you directly use @ComponentScan (i.e. not via @SpringBootApplication ) you will need to register the TypeExcludeFilter with it. See the Javadoc for details. |
40.3.3 Working with random ports
If you need to start a full running server for tests, we recommend that you use random ports. If you use @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
an available port will be picked at random each time your test runs.
The @LocalServerPort
annotation can be used to inject the actual port used into your test. For convenience, tests that need to make REST calls to the started server can additionally @Autowire
a TestRestTemplate
which will resolve relative links to the running server.
import org.junit.*; import org.junit.runner.*; import org.springframework.boot.test.context.web.*; import org.springframework.boot.test.web.client.*; import org.springframework.test.context.junit4.*; import static org.assertj.core.api.Assertions.* _@RunWith(SpringRunner.class)_ _@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)_ public class MyWebIntegrationTests { _@Autowired_ private TestRestTemplate restTemplate; _@Test_ public void exampleTest() { String body = this.restTemplate.getForObject("/", String.class); assertThat(body).isEqualTo("Hello World"); } }
40.3.4 Mocking and spying beans
It’s sometimes necessary to mock certain components within your application context when running tests. For example, you may have a facade over some remote service that’s unavailable during development. Mocking can also be useful when you want to simulate failures that might be hard to trigger in a real environment.
Spring Boot includes a @MockBean
annotation that can be used to define a Mockito mock for a bean inside your ApplicationContext
. You can use the annotation to add new beans, or replace a single existing bean definition. The annotation can be used directly on test classes, on fields within your test, or on @Configuration
classes and fields. When used on a field, the instance of the created mock will also be injected. Mock beans are automatically reset after each test method.
Here’s a typical example where we replace an existing RemoteService
bean with a mock implementation:
import org.junit.*; import org.junit.runner.*; import org.springframework.beans.factory.annotation.*; import org.springframework.boot.test.context.*; import org.springframework.boot.test.mock.mockito.*; import org.springframework.test.context.junit4.*; import static org.assertj.core.api.Assertions.*; import static org.mockito.BDDMockito.*; _@RunWith(SpringRunner.class)_ _@SpringBootTest_ public class MyTests { _@MockBean_ private RemoteService remoteService; _@Autowired_ private Reverser reverser; _@Test_ public void exampleTest() { // RemoteService has been injected into the reverser bean given(this.remoteService.someCall()).willReturn("mock"); String reverse = reverser.reverseSomeCall(); assertThat(reverse).isEqualTo("kcom"); } }
Additionally you can also use @SpyBean
to wrap any existing bean with a Mockito spy
. See the Javadoc for full details.
40.3.5 Auto-configured tests
Spring Boot’s auto-configuration system works well for applications, but can sometimes be a little too much for tests. It’s often helpful to load only the parts of the configuration that are required to test a ‘slice’ of your application. For example, you might want to test that Spring MVC controllers are mapping URLs correctly, and you don’t want to involve database calls in those tests; or you might be wanting to test JPA entities, and you’re not interested in web layer when those tests run.
The spring-boot-test-autoconfigure
module includes a number of annotations that can be used to automatically configure such ‘slices’. Each of them works in a similar way, providing a @…Test
annotation that loads the ApplicationContext
and one or more @AutoConfigure…
annotations that can be used to customize auto-configuration settings.
Tip | |
---|---|
It’s also possible to use the @AutoConfigure… annotations with the standard @SpringBootTest annotation. You can use this combination if you’re not interested in ‘slicing’ your application but you want some of the auto-configured test beans. |
40.3.6 Auto-configured JSON tests
To test that Object JSON serialization and deserialization is working as expected you can use the @JsonTest
annotation. @JsonTest
will auto-configure Jackson ObjectMapper
, any @JsonComponent
beans and any Jackson Modules
. It also configures Gson
if you happen to be using that instead of, or as well as, Jackson. If you need to configure elements of the auto-configuration you can use the @AutoConfigureJsonTesters
annotation.
Spring Boot includes AssertJ based helpers that work with the JSONassert and JsonPath libraries to check that JSON is as expected. The JacksonHelper
, GsonHelper
and BasicJsonTester
classes can be used for Jackson, Gson and Strings respectively. Any helper fields on the test class can be @Autowired
when using @JsonTest
.
import org.junit.*; import org.junit.runner.*; import org.springframework.beans.factory.annotation.*; import org.springframework.boot.test.autoconfigure.json.*; import org.springframework.boot.test.context.*; import org.springframework.boot.test.json.*; import org.springframework.test.context.junit4.*; import static org.assertj.core.api.Assertions.*; _@RunWith(SpringRunner.class)_ _@JsonTest_ public class MyJsonTests { _@Autowired_ private JacksonTester<VehicleDetails> json; _@Test_ public void testSerialize() throws Exception { VehicleDetails details = new VehicleDetails("Honda", "Civic"); // Assert against a `.json` file in the same package as the test assertThat(this.json.write(details)).isEqualToJson("expected.json"); // Or use JSON path based assertions assertThat(this.json.write(details)).hasJsonPathStringValue("@.make"); assertThat(this.json.write(details)).extractingJsonPathStringValue("@.make") .isEqualTo("Honda"); } _@Test_ public void testDeserialize() throws Exception { String content = "{\"make\":\"Ford\",\"model\":\"Focus\"}"; assertThat(this.json.parse(content)) .isEqualTo(new VehicleDetails("Ford", "Focus")); assertThat(this.json.parseObject(content).getMake()).isEqualTo("Ford"); } }
Note | |
---|---|
JSON helper classes can also be used directly in standard unit tests. Simply call the initFields method of the helper in your @Before method if you aren’t using @JsonTest . |
A list of the auto-configuration that is enabled by @JsonTest
can be found in the appendix.
40.3.7 Auto-configured Spring MVC tests
To test Spring MVC controllers are working as expected you can use the @WebMvcTest
annotation. @WebMvcTest
will auto-configure the Spring MVC infrastructure and limit scanned beans to @Controller
, @ControllerAdvice
, @JsonComponent
, Filter
, WebMvcConfigurer
and HandlerMethodArgumentResolver
. Regular @Component
beans will not be scanned when using this annotation.
Often @WebMvcTest
will be limited to a single controller and used in combination with @MockBean
to provide mock implementations for required collaborators.
@WebMvcTest
also auto-configures MockMvc
. Mock MVC offers a powerful way to quickly test MVC controllers without needing to start a full HTTP server.
Tip | |
---|---|
You can also auto-configure MockMvc in a non-@WebMvcTest (e.g. SpringBootTest ) by annotating it with @AutoConfigureMockMvc . |
import org.junit.*; import org.junit.runner.*; import org.springframework.beans.factory.annotation.*; import org.springframework.boot.test.autoconfigure.web.servlet.*; import org.springframework.boot.test.mock.mockito.*; import static org.assertj.core.api.Assertions.*; import static org.mockito.BDDMockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; _@RunWith(SpringRunner.class)_ _@WebMvcTest(UserVehicleController.class)_ public class MyControllerTests { _@Autowired_ private MockMvc mvc; _@MockBean_ private UserVehicleService userVehicleService; _@Test_ public void testExample() throws Exception { given(this.userVehicleService.getVehicleDetails("sboot")) .willReturn(new VehicleDetails("Honda", "Civic")); this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN)) .andExpect(status().isOk()).andExpect(content().string("Honda Civic")); } }
Tip | |
---|---|
If you need to configure elements of the auto-configuration (for example when servlet filters should be applied) you can use attributes in the @AutoConfigureMockMvc annotation. |
If you use HtmlUnit or Selenium, auto-configuration will also provide a WebClient
bean and/or a WebDriver
bean. Here is an example that uses HtmlUnit:
import com.gargoylesoftware.htmlunit.*; import org.junit.*; import org.junit.runner.*; import org.springframework.beans.factory.annotation.*; import org.springframework.boot.test.autoconfigure.web.servlet.*; import org.springframework.boot.test.mock.mockito.*; import static org.assertj.core.api.Assertions.*; import static org.mockito.BDDMockito.*; _@RunWith(SpringRunner.class)_ _@WebMvcTest(UserVehicleController.class)_ public class MyHtmlUnitTests { _@Autowired_ private WebClient webClient; _@MockBean_ private UserVehicleService userVehicleService; _@Test_ public void testExample() throws Exception { given(this.userVehicleService.getVehicleDetails("sboot")) .willReturn(new VehicleDetails("Honda", "Civic")); HtmlPage page = this.webClient.getPage("/sboot/vehicle.html"); assertThat(page.getBody().getTextContent()).isEqualTo("Honda Civic"); } }
A list of the auto-configuration that is enabled by @WebMvcTest
can be found in the appendix.
40.3.8 Auto-configured Data JPA tests
@DataJpaTest
can be used if you want to test JPA applications. By default it will configure an in-memory embedded database, scan for @Entity
classes and configure Spring Data JPA repositories. Regular @Component
beans will not be loaded into the ApplicationContext
.
Data JPA tests are transactional and rollback at the end of each test by default, see the docs.spring.io/spring/docs/4.3.3.RELEASE/spring-framework-reference/htmlsingle#testcontext-tx-enabling-transactions [relevant section] in the Spring Reference Documentation for more details. If that’s not what you want, you can disable transaction management for a test or for the whole class as follows:
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; _@RunWith(SpringRunner.class)_ _@DataJpaTest_ _@Transactional(propagation = Propagation.NOT_SUPPORTED)_ public class ExampleNonTransactionalTests { }
Data JPA tests may also inject a TestEntityManager
bean which provides an alternative to the standard JPA EntityManager
specifically designed for tests. If you want to use TestEntityManager
outside of @DataJpaTests
you can also use the @AutoConfigureTestEntityManager
annotation. A JdbcTemplate
is also available should you need that.
import org.junit.*; import org.junit.runner.*; import org.springframework.boot.test.autoconfigure.orm.jpa.*; import static org.assertj.core.api.Assertions.*; _@RunWith(SpringRunner.class)_ _@DataJpaTest_ public class ExampleRepositoryTests { _@Autowired_ private TestEntityManager entityManager; _@Autowired_ private UserRepository repository; _@Test_ public void testExample() throws Exception { this.entityManager.persist(new User("sboot", "1234")); User user = this.repository.findByUsername("sboot"); assertThat(user.getUsername()).isEqualTo("sboot"); assertThat(user.getVin()).isEqualTo("1234"); } }
In-memory embedded databases generally work well for tests since they are fast and don’t require any developer installation. If, however, you prefer to run tests against a real database you can use the @AutoConfigureTestDatabase
annotation:
_@RunWith(SpringRunner.class)_ _@DataJpaTest_ _@AutoConfigureTestDatabase(replace=Replace.NONE)_ public class ExampleRepositoryTests { // ... }
A list of the auto-configuration that is enabled by @DataJpaTest
can be found in the appendix.
40.3.9 Auto-configured REST clients
The @RestClientTest
annotation can be used if you want to test REST clients. By default it will auto-configure Jackson and GSON support, configure a RestTemplateBuilder
and add support for MockRestServiceServer
. The specific beans that you want to test should be specified using value
or components
attribute of @RestClientTest
:
_@RunWith(SpringRunner.class)_ _@RestClientTest(RemoteVehicleDetailsService.class)_ public class ExampleRestClientTest { _@Autowired_ private RemoteVehicleDetailsService service; _@Autowired_ private MockRestServiceServer server; _@Test_ public void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() throws Exception { this.server.expect(requestTo("/greet/details")) .andRespond(withSuccess("hello", MediaType.TEXT_PLAIN)); String greeting = this.service.callRestService(); assertThat(greeting).isEqualTo("hello"); } }
A list of the auto-configuration that is enabled by @RestClientTest
can be found in the appendix.
40.3.10 Auto-configured Spring REST Docs tests
The @AutoConfigureRestDocs
annotation can be used if you want to use Spring REST Docs in your tests. It will automatically configure MockMvc
to use Spring REST Docs and remove the need for Spring REST Docs' JUnit rule.
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; _@RunWith(SpringRunner.class)_ _@WebMvcTest(UserController.class)_ _@AutoConfigureRestDocs("target/generated-snippets")_ public class UserDocumentationTests { _@Autowired_ private MockMvc mvc; _@Test_ public void listUsers() throws Exception { this.mvc.perform(get("/users").accept(MediaType.TEXT_PLAIN)) .andExpect(status().isOk()) .andDo(document("list-users")); } }
In addition to configuring the output directory, @AutoConfigureRestDocs
can also configure the host, scheme, and port that will appear in any documented URIs. If you require more control over Spring REST Docs' configuration a RestDocsMockMvcConfigurationCustomizer
bean can be used:
_@TestConfiguration_ static class CustomizationConfiguration implements RestDocsMockMvcConfigurationCustomizer { _@Override_ public void customize(MockMvcRestDocumentationConfigurer configurer) { configurer.snippets().withTemplateFormat(TemplateFormats.markdown()); } }
If you want to make use of Spring REST Docs' support for a parameterized output directory, you can create a RestDocumentationResultHandler
bean. The auto-configuration will call alwaysDo
with this result handler, thereby causing each MockMvc
call to automatically generate the default snippets:
_@TestConfiguration_ static class ResultHandlerConfiguration { _@Bean_ public RestDocumentationResultHandler restDocumentation() { return MockMvcRestDocumentation.document("{method-name}"); } }
40.3.11 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. Exactly how you can use Spock to test a Spring Boot application depends on the version of Spock that you are using.
Note | |
---|---|
Spring Boot provides dependency management for Spock 1.0. If you wish to use Spock 1.1 you should override the spock.version property in your build.gradle or pom.xml file. |
When using Spock 1.1, the annotations described above can only be used and you can annotate your Specification
with @SpringBootTest
to suit the needs of your tests.
When using Spock 1.0, @SpringBootTest
will not work for a web project. You need to use @SpringApplicationConfiguration
and @WebIntegrationTest(randomPort = true)
. Being unable to use @SpringBootTest
means that you also lose the auto-configured TestRestTemplate
bean. You can create an equivalent bean yourself using the following configuration:
_@Configuration_ static class TestRestTemplateConfiguration { _@Bean_ public TestRestTemplate testRestTemplate( ObjectProvider<RestTemplateBuilder> builderProvider, Environment environment) { RestTemplateBuilder builder = builderProvider.getIfAvailable(); TestRestTemplate template = builder == null ? new TestRestTemplate() : new TestRestTemplate(builder.build()); template.setUriTemplateHandler(new LocalHostUriTemplateHandler(environment)); return template; } }