Spring Boot provides a number of utilities and annotations that make it easier to test your application. Testing support is provided by two modules: spring-boot-test
contains the core elements, and spring-boot-test-autoconfigure
supports autoconfiguration for tests.
Most developers use the "starter" spring-boot-starter-test
, which imports both Spring Boot test modules, as well as JUnit Jupiter, AssertJ, Hamcrest and a number of other useful libraries.
If you have tests that use JUnit 4, you can use the vintage JUnit 5 engine to run them. To use the vintage engine, add a dependency on junit-vintage-engine
, as shown in the following example:
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
hamcrest-core
dropped in favor of org.hamcrest:hamcrest
, which is part of spring-boot-starter-test
.
Availability Test Area Dependencies
"starter" spring-boot-starter-test
(in test
scope
) contains the following provided libraries:
JUnit 5: The de facto standard for unit testing of Java applications.
Spring Test and Spring Boot Test: Tools to support utilities and integration tests for Spring Boot applications.
AssertJ: Fluid assertion library.
Hamcrest: Library of matchers (also known as constraints or predicates) .
Mockito: Java framework for mocking )
JSONassert: JSON assertion library.
JsonPath: XPath for JSON.
We usually find these common libraries useful when writing tests. If these libraries do not meet your needs, you can add additional test dependencies as you wish.
Testing Spring Applications
One of the main benefits of dependency injection is that it should make it easier to modularize code testing. It is possible to instantiate objects using the new
operator without even involving Spring. You can also use mock objectsinstead of real dependencies.
Often you want to go beyond unit testing and start integrating testing (with ApplicationContext
from Spring). It is useful to be able to perform integration testing without necessarily deploying the application or connecting to other infrastructure.
Spring Framework includes a special test module for such integration testing. You can declare a dependency directly on org.springframework:spring-test
or use the "starter" spring-boot-starter-test
for transitive connection.
Testing Spring Boot Applications
A Spring Boot application is an ApplicationContext
for Spring, so nothing special is required to test it other than what you do for a vanilla Spring context.
SpringApplication
to create it.
Spring Boot provides a @SpringBootTest
annotation that can be used as an alternative to the standard @ContextConfiguration
annotation for spring-test
when the need arises in Spring Boot features. The annotation creates an ApplicationContext
used in tests via SpringApplication
. In addition to the @SpringBootTest
annotation, there are also a number of other annotations for testing more specific slices of the application.
@RunWith(SpringRunner.class)
to the test, otherwise the annotations will be ignored. If you are using JUnit 5, there is no need to add the equivalent of the
@ExtendWith(SpringExtension.class)
annotation, since the
@SpringBootTest
annotation and the other
@…Test
are already annotated with it.
By default, the @SpringBootTest
annotation does not start the server. You can use the webEnvironment
attribute for the @SpringBootTest
annotation to further refine the progress of your tests:
MOCK
(default) : Loads the web contextApplicationContext
and provides a mock web environment object. Embedded servers do not start when using this annotation. If the web environment is not available in your classpath, this mode transparently falls back to creating a regular non-webApplicationContext
. It can be used in combination with the@AutoConfigureMockMvc
or@AutoConfigureWebTestClient
annotation for mock-based testing of a web application.RANDOM_PORT
: Loads theWebServerApplicationContext
and provides the actual web environment. Embedded servers start and listen on a random port.DEFINED_PORT
: Loads theWebServerApplicationContext
and provides the actual web environment. Embedded servers start and listen on the specified port (from theapplication.properties
file) or the default port8080
.NONE
: LoadsApplicationContext
usingSpringApplication
, but does not provide any web environment (mock or otherwise).
@Transactional
annotation, it by default rolls back the transaction at the end of each test method . However, since using this scheme with
RANDOM_PORT
or
DEFINED_PORT
implicitly exposes the actual servlet environment, the HTTP client and server run in separate threads and thus in separate transactions. Any transaction initiated on the server is not rolled back in this case.
@SpringBootTest
annotation with
webEnvironment = WebEnvironment .RANDOM_PORT
also starts the management server on a separate random port if the application uses a different port for the management server.
Defining the type of web application
If Spring MVC is available, the normal one is configured MVC based application context. If only Spring WebFlux is present, this will be determined and the application context will be configured based on WebFlux.
If both frameworks are present, Spring MVC takes precedence. If you want to test a reactive web application in this scenario, you need to set the spring.main.web-application-type
property:
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(properties = "spring.main.web-application-type=reactive")
class MyWebFluxTests {
// ...
}
import org.springframework.boot.test.context.SpringBootTest
@SpringBootTest(properties = ["spring.main.web-application-type=reactive"])
class MyWebFluxTests {
// ...
}
Defining a test configuration
If you're familiar with the Spring Test Framework, you're probably used to using @ContextConfiguration(classes=…)
to specify which @Configuration
for Spring should be loaded. Alternatively, you could often use nested classes with the @Configuration
annotation in your test.
When testing Spring Boot applications, this is usually not required. The @*Test
annotations in Spring Boot search for your primary configuration automatically if you have not explicitly defined one.
The search algorithm starts from the package containing the test until it finds the class , annotated with @SpringBootApplication
or @SpringBootConfiguration
. Provided you have structured your code adequately, the basic configuration can usually be found.
If you are using a test annotation to test a more specific layer of the application , then you should avoid adding domain-specific configuration options in the main method's application class.
The basic component scanning configuration, which includes the @SpringBootApplication
annotation, defines the exclusion filters that are used to ensure that the slice acquisition works as expected. If you use an explicit directive with the @ComponentScan
annotation on a class marked with the @SpringBootApplication
annotation, be aware that these filters will be disabled. If you resort to obtaining slices, you should define them again.
If you need to configure the primary configuration, you can use a nested class marked with the @TestConfiguration
annotation. Unlike a nested class marked with the @Configuration
annotation, which is used in place of the application's main configuration, a nested class with the @TestConfiguration
annotation is used in addition to the application's main configuration.
Test configuration exception
If the application uses component scanning (for example, when using the @SpringBootApplication
or @ComponentScan
annotations), you may find that high-level configuration classes that were created only for certain tests are accidentally intercepted throughout.
The @TestConfiguration
annotation can be used in an internal test class to configure the primary configuration. When the @TestConfiguration
annotation marks a high-level class, it specifies that classes in src/test/java
should not be intercepted during scanning. You can then import this class explicitly when needed, as shown in the following example:
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
@SpringBootTest
@Import(MyTestsConfiguration.class)
class MyTests {
@Test
void exampleTest() {
// ...
}
}
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.context.annotation.Import
@SpringBootTest
@Import(MyTestsConfiguration::class)
class MyTests {
@Test
fun exampleTest() {
// ...
}
}
@ComponentScan
annotation is used directly (that is, not through the
@SpringBootApplication
annotation), you must register the
TypeExcludeFilter
with it.
Using Application Arguments
If your application accepts arguments, you can use the @SpringBootTest
annotation to inject them through the args
attribute.
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(args = "--app.test=one")
class MyApplicationArgumentTests {
@Test
void applicationArgumentsPopulated(@Autowired ApplicationArguments args) {
assertThat(args.getOptionNames()).containsOnly("app.test");
assertThat(args.getOptionValues("app.test")).containsOnly("one");
}
}
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.ApplicationArguments
import org.springframework.boot.test.context.SpringBootTest
@SpringBootTest(args = ["--app.test=one"])
class MyApplicationArgumentTests {
@Test
fun applicationArgumentsPopulated(@Autowired args: ApplicationArguments) {
assertThat(args.optionNames).containsOnly("app.test")
assertThat(args.getOptionValues("app.test")).containsOnly("one")
}
}
Testing with a mock environment
By default, the @SpringBootTest
annotation does not start the server, but instead creates a mock environment for testing end webs -points.
In Spring MVC, you can query web endpoints using MockMvc
or WebTestClient
, as shown in the following example:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
class MyMockMvcTests {
@Test
void testWithMockMvc(@Autowired MockMvc mvc) throws Exception {
mvc.perform(get("/")).andExpect(status().isOk()).andExpect(content().string("Hello World"));
}
// If Spring WebFlux is in the classpath, then you can manage MVC tests using WebTestClient
@Test
void testWithWebTestClient(@Autowired WebTestClient webClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World");
}
}
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
@SpringBootTest
@AutoConfigureMockMvc
class MyMockMvcTests {
@Test
fun testWithMockMvc(@Autowired mvc: MockMvc) {
mvc.perform(MockMvcRequestBuilders.get("/")).andExpect(MockMvcResultMatchers.status().isOk)
.andExpect(MockMvcResultMatchers.content().string("Hello World"))
}
// If Spring WebFlux is in the classpath, then you can manage MVC tests using WebTestClient
@Test
fun testWithWebTestClient(@Autowired webClient: WebTestClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk
.expectBody<String>().isEqualTo("Hello World")
}
}
ApplicationContext
, then consider using the
@WebMvcTest
annotation as an alternative.
You can use WebTestClient
with Spring WebFlux endpoints as shown in the following example:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;
@SpringBootTest
@AutoConfigureWebTestClient
class MyMockWebTestClientTests {
@Test
void exampleTest(@Autowired WebTestClient webClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World");
}
}
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody
@SpringBootTest
@AutoConfigureWebTestClient
class MyMockWebTestClientTests {
@Test
fun exampleTest(@Autowired webClient: WebTestClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk
.expectBody<String>().isEqualTo("Hello World")
}
}
Testing in a simulation environment is usually faster than when using a populated servlet container. However, because mocking occurs at the Spring MVC level, code that relies on lower-level servlet container logic cannot be directly tested with MockMvc.
For example, Spring Boot's error handling relies on " error pages" provided by the servlet container. This means that while it is possible to verify that the MVC layer throws and handles exceptions as intended, it is not possible to directly verify that a particular custom error page is rendered. If you need to test these low-level nuances, you can run a fully functioning server, as described in the next section.
Testing with a running server
If you want to run a fully functioning server, then We recommend using random ports. If you use @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
, the available port is chosen randomly each time the test is run.
The @LocalServerPort
annotation can be used to in order to inject the actually used port into the test. For convenience, tests that need to make REST calls to a running server can optionally bind a WebTestClient
via the @Autowire
annotation, which allows relative references to the running server and has a special API for testing responses, as shown in the following example:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.web.reactive.server.WebTestClient;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortWebTestClientTests {
@Test
void exampleTest(@Autowired WebTestClient webClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World");
}
}
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortWebTestClientTests {
@Test
fun exampleTest(@Autowired webClient: WebTestClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk
.expectBody<String>().isEqualTo("Hello World")
}
}
WebTestClient
you can use for both real servers and mock environments.
This configuration requires spring-webflux
to be present in the classpath. If you can't or don't want to add webflux, Spring Boot also provides the ability to use TestRestTemplate
:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortTestRestTemplateTests {
@Test
void exampleTest(@Autowired TestRestTemplate restTemplate) {
String body = restTemplate.getForObject("/", String.class);
assertThat(body).isEqualTo("Hello World");
}
}
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.boot.test.web.client.TestRestTemplate
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortTestRestTemplateTests {
@Test
fun exampleTest(@Autowired restTemplate: TestRestTemplate) {
val body = restTemplate.getForObject("/", String::class.java)
assertThat(body).isEqualTo("Hello World")
}
}
Configuring WebTestClient
To configure the WebTestClient
bean, configure the WebTestClientBuilderCustomizer
bean . Any such beans are called using the WebTestClient.Builder
, which is used to create the WebTestClient
.
Using JMX
Because the test context framework caches context, JMX technology is disabled by default to prevent identical beans from being registered in the same domain. If such a test requires access to MBeanServer
, consider marking it as dirty:
import javax.management.MBeanServer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(properties = "spring.jmx.enabled=true")
@DirtiesContext
class MyJmxTests {
@Autowired
private MBeanServer mBeanServer;
@Test
void exampleTest() {
assertThat(this.mBeanServer.getDomains()).contains("java.lang");
// ...
}
}
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.annotation.DirtiesContext
import javax.management.MBeanServer
@SpringBootTest(properties = ["spring.jmx.enabled=true"])
@DirtiesContext
class MyJmxTests(@Autowired val mBeanServer: MBeanServer) {
@Test
fun exampleTest() {
assertThat(mBeanServer.domains).contains("java.lang")
// ...
}
}
Using Metrics
Regardless of the classpath, counter registries other than those stored in memory are not automatically configured when using the @SpringBootTest
annotation.
If you need to export metrics to another backend as part of an integration test, annotate it with @AutoConfigureMetrics
.
GO TO FULL VERSION