Mock beans and spy beans
When running tests, you sometimes need to mock certain components in the application context. For example, you might have a facade for some remote service that is not accessible during development. Mocking can also be useful if you need to simulate crashes that are difficult to cause in a real environment.
Spring Boot contains a @MockBean
annotation that can be used to define a Mockito mock for a bean inside ApplicationContext
. You can use an annotation to add new beans or replace one existing bean definition. The annotation can be used directly on test classes, on fields within a test, or on classes and fields with the @Configuration
annotation. When used with a field, an instance of the created mock object is also injected. Mock beans are automatically reset after each test method is executed.
If the test uses one of Spring Boot's test annotations (for example, @SpringBootTest
), this feature is automatically enabled. To use this feature with another organization type, you must explicitly add listeners, as shown in the following example:
import org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener;
import org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
@ContextConfiguration(classes = MyConfig.class)
@TestExecutionListeners({ MockitoTestExecutionListener.class, ResetMocksTestExecutionListener.class })
class MyTests {
// ...
}
import org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener
import org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.TestExecutionListeners
@ContextConfiguration(classes = [MyConfig::class])
@TestExecutionListeners(
MockitoTestExecutionListener::class,
ResetMocksTestExecutionListener::class
)
class MyTests {
// ...
}
The following example replaces an existing RemoteService
bean with a mock implementation:
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.mock.mockito.MockBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
@SpringBootTest
class MyTests {
@Autowired
private Reverser reverser;
@MockBean
private RemoteService remoteService;
@Test
void exampleTest() {
given(this.remoteService.getValue()).willReturn("spring");
String reverse = this.reverser.getReverseValue(); // Calls injected RemoteService
assertThat(reverse).isEqualTo("gnirps");
}
}
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.mock.mockito.MockBean
@SpringBootTest
class MyTests(@Autowired val reverser: Reverser, @MockBean val remoteService: RemoteService) {
@Test
fun exampleTest() {
given(remoteService.value).willReturn("spring")
val reverse = reverser.reverseValue // Calls injected RemoteService
assertThat(reverse).isEqualTo("gnirps")
}
}
@MockBean
annotation cannot be used to mock logic the bean that runs when the application context is updated. By the time the test is executed, the update of the application context will have already been completed, and it will be too late to configure the simulated operating logic. In this situation, we recommend using a method marked with the
@Bean
annotation to create and configure the mock object.
Alternatively, you can use the @SpyBean
annotation to wrap any existing bean with a spy
object from Mockito.
final
. This stops the Mockito framework from working correctly because it cannot mock or follow
final
methods in its default configuration. If you need to mock or monitor such a bean, configure Mockito to use an inline mock object constructor by adding
org.mockito:mockito-inline
to your application's test dependencies. This allows Mockito to mock and follow
final
methods.
@MockBean
or
@SpyBean
annotations affects the cache key, which will likely increase the number of contexts.
@SpyBean
annotation to keep track of beans with
@Cacheable
annotated methods that reference parameters by name, your application must be compiled with the
-parameters
option. This way, the caching infrastructure is guaranteed to have access to the parameter names once the bean is discovered.
@SpyBean
used to monitor the bean being proxied in Spring, in some situations it may be necessary to remove the Spring proxy, for example, when setting expected events using
given
or
when
. This is done using
AopTestUtils.getTargetObject(yourProxiedSpy)
.
Auto-configuring tests
Spring Boot's auto-configuration system works great with applications, but can sometimes be too redundant for tests. It is often practical to load only those parts of the configuration that are needed to test the "layer" of the application. For example, you might want to ensure that Spring MVC controllers render URLs correctly, but don't want to involve database calls in those tests, or you might want to test JPA entities, but the web tier isn't interested in running those tests. .
The spring-boot-test-autoconfigure
module contains a number of annotations that can be used to automatically configure such "layers". Each of them works the same way, representing an annotation of the form @...Test
, which loads ApplicationContext
, and one or more annotations of the form @AutoConfigure...
, which can be used to configure auto-configuration parameters.
@...Test
provide an attribute
excludeAutoConfiguration
. As an alternative, you can use the
@ImportAutoConfiguration#exclude
annotation.
@…Test
into one test are not supported. If multiple "layers" are required, select one of the
@...Test
annotations and add
@AutoConfigure...
annotations to the other "layers" manually.
@AutoConfigure...
with the standard annotation
@SpringBootTest
. You can use this combination if you are not interested in creating layers of your application, but you require some of the auto-configured test beans.
Auto-configured JSON tests
To ensure that JSON serialization and deserialization -objects works as expected, you can use the @JsonTest
annotation. The @JsonTest
annotation automatically configures an available supported JSON mapper, which can be one of the following libraries:
ObjectMapper
from Jackson, any beans with the@JsonComponent
annotation and anyModules
from JacksonGson
Jsonb
@JsonTest
annotation can be found in the documentation appendix.
If you need to configure autoconfig elements, you can use the @AutoConfigureJsonTesters
annotation.
Spring Boot contains AssertJ-based helper classes that work with the JSONAssert and JsonPath libraries, designed to check that the intended JSON mapping is correct. The classes JacksonTester
, GsonTester
, JsonbTester
and BasicJsonTester
can be used for the Jackson, Gson, Jsonb and Strings libraries, respectively. Any auxiliary fields of the test class can be marked with the @Autowired
annotation when using the @JsonTest
annotation. The following example shows a test class for the Jackson library:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.boot.test.json.JacksonTester;
import static org.assertj.core.api.Assertions.assertThat;
@JsonTest
class MyJsonTests {
@Autowired
private JacksonTester<VehicleDetails> json;
@Test
void serialize() throws Exception {
VehicleDetails details = new VehicleDetails("Honda", "Civic");
// Assertion for a ".json" file in the same package as the test
assertThat(this.json.write(details)).isEqualToJson("expected.json");
// Or use assertions based on the JSON path
assertThat(this.json.write(details)).hasJsonPathStringValue("@.make");
assertThat(this.json.write(details)).extractingJsonPathStringValue("@.make").isEqualTo("Honda");
}
@Test
void deserialize() 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");
}
}
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.autoconfigure.json.JsonTest
import org.springframework.boot.test.json.JacksonTester
@JsonTest
class MyJsonTests(@Autowired val json: JacksonTester<VehicleDetails>) {
@Test
fun serialize() {
val details = VehicleDetails("Honda", "Civic")
// Assertion for a ".json" file in the same package as the test
assertThat(json.write(details)).isEqualToJson("expected.json")
// Or use assertions based on JSON path
assertThat(json.write(details)).hasJsonPathStringValue("@.make")
assertThat(json.write(details)).extractingJsonPathStringValue("@.make").isEqualTo("Honda")
}
@Test
fun deserialize() {
val content = "{\"make\":\"Ford\",\"model\":\"Focus\"}"
assertThat(json.parse(content)).isEqualTo(VehicleDetails("Ford", "Focus"))
assertThat(json.parseObject(content).make).isEqualTo("Ford")
}
}
initFields
method of the helper class in your method annotated with
@Before
if the
@JsonTest
annotation is not used.
If you use the AssertJ-based Spring Boot helper classes to add an assertion for a number value in a given JSON path, you may not need to use isEqualTo
depending on the type. Instead, you can use the satisfies
function in AssertJ to add an assertion that a value satisfies a given condition. For example, the following example adds a statement that the actual number is a float value close to 0.15
within offset 0.01
.
@Test
void someTest() throws Exception {
SomeObject value = new SomeObject(0.152f);
assertThat(this.json.write(value)).extractingJsonPathNumberValue("@.test.numberValue")
.satisfies((number) -> assertThat(number.floatValue()).isCloseTo(0.15f, within(0.01f)));
}
@Test
fun someTest() {
val value = SomeObject(0.152f)
assertThat(json.write(value)).extractingJsonPathNumberValue("@.test.numberValue")
.satisfies(ThrowingConsumer { number ->
assertThat(number.toFloat()).isCloseTo(0.15f, within(0.01f))
})
}
Spring MVC auto-configurable tests
To test Spring MVC controllers for intended functionality, use the @WebMvcTest
annotation. The @WebMvcTest
annotation automatically configures the Spring MVC framework and limits scanned beans to @Controller
, @ControllerAdvice
, @JsonComponent
, Converter
, GenericConverter
, Filter
, HandlerInterceptor
, WebMvcConfigurer
, WebMvcRegistrations
and HandlerMethodArgumentResolver
. Regular beans with @Component
and @ConfigurationProperties
annotations are not scanned when using the @WebMvcTest
annotation. To add beans marked with the @ConfigurationProperties
annotation, you can use the @EnableConfigurationProperties
annotation.
Module
from Jackson, you can import additional configuration classes using the
@Import
annotation in your test.
Often the @WebMvcTest
is limited to a single controller and is used in conjunction with the @MockBean
annotation to pass mock implementations for the required communicating objects.
@WebMvcTest
also automatically configures MockMvc
. Mock MVC provides an efficient way to quickly test MVC controllers without having to run a full HTTP server.
MockMvc
without the
@WebMvcTest
annotation (and for example, with the
@SpringBootTest
annotation), annotating it with
@AutoConfigureMockMvc
. The following example uses
MockMvc
:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.BDDMockito.given;
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;
@WebMvcTest(UserVehicleController.class)
class MyControllerTests {
@Autowired
private MockMvc mvc;
@MockBean
private UserVehicleService userVehicleService;
@Test
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"));
}
}
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
@WebMvcTest(UserVehicleController::class)
class MyControllerTests(@Autowired val mvc: MockMvc) {
@MockBean
lateinit var userVehicleService: UserVehicleService
@Test
fun testExample() {
given(userVehicleService.getVehicleDetails("sboot"))
.willReturn(VehicleDetails("Honda", "Civic"))
mvc.perform(MockMvcRequestBuilders.get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
.andExpect(MockMvcResultMatchers.status().isOk)
.andExpect(MockMvcResultMatchers.content().string("Honda Civic"))
}
}
@AutoConfigureMockMvc
annotation .
If you are using HtmlUnit and Selenium, auto-configuration also provides a WebClient
bean for HtmlUnit and/or a WebDriver
bean for Selenium. The following example uses HtmlUnit:
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
@WebMvcTest(UserVehicleController.class)
class MyHtmlUnitTests {
@Autowired
private WebClient webClient;
@MockBean
private UserVehicleService userVehicleService;
@Test
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");
}
}
import com.gargoylesoftware.htmlunit.WebClient
import com.gargoylesoftware.htmlunit.html.HtmlPage
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.mock.mockito.MockBean
@WebMvcTest(UserVehicleController::class)
class MyHtmlUnitTests(@Autowired val webClient: WebClient) {
@MockBean
lateinit var userVehicleService: UserVehicleService
@Test
fun testExample() {
given(userVehicleService.getVehicleDetails("sboot")).willReturn(VehicleDetails("Honda", "Civic"))
val page = webClient.getPage<HtmlPage>("/sboot/vehicle.html")
assertThat(page.body.textContent).isEqualTo("Honda Civic")
}
}
WebDriver
into a special "availability area" to ensure that the driver exits after each test and a new instance is deployed. If this operating logic is unnecessary, you can add the
@Scope("singleton")
annotation to the definition of your
WebDriver
with the
@Bean
annotation.
webDriver
accessibility scope created by Spring Boot will replace any user-defined accessibility scope with the same name. If you have your own
webDriver
accessibility scope defined, you may find that it stops working when you use the
@WebMvcTest
annotation.
If the classpath has the Spring Security annotation @WebMvcTest
will also scan the WebSecurityConfigurer
beans. Instead of completely disabling security for such tests, you can use the test support from Spring Security.
Spring WebFlux Self-Configuring Tests
To ensure that Spring WebFlux controllers work as expected, you can use the @WebFluxTest
annotation. The @WebFluxTest
annotation automatically configures the Spring WebFlux framework and limits scanned beans to @Controller
, @ControllerAdvice
, @JsonComponent
, Converter
, GenericConverter
, WebFilter
and WebFluxConfigurer
. Regular beans with @Component
and @ConfigurationProperties
annotations are not scanned when using the @WebFluxTest
annotation. To add beans marked with the @ConfigurationProperties
annotation, you can use the @EnableConfigurationProperties
annotation.
Module
from the Jackson library, you can import additional configuration classes using the
@Import
annotation in the test.
Often the @WebFluxTest
is limited to a single controller and is used in conjunction with the @MockBean
annotation to pass mock implementations for the required communicating objects.
The @WebFluxTest
annotation code> also automatically configures WebTestClient
, which provides an efficient means of quickly testing WebFlux controllers without the need to run a full HTTP server.
WebTestClient
without the
@WebFluxTest
annotation (and for example with the
@SpringBootTest
annotation) by annotating it with
@AutoConfigureWebTestClient
. The following example shows a class that uses the
@WebFluxTest
and
WebTestClient
annotation:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.mockito.BDDMockito.given;
@WebFluxTest(UserVehicleController.class)
class MyControllerTests {
@Autowired
private WebTestClient webClient;
@MockBean
private UserVehicleService userVehicleService;
@Test
void testExample() {
given(this.userVehicleService.getVehicleDetails("sboot"))
.willReturn(new VehicleDetails("Honda", "Civic"));
this.webClient.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN).exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Honda Civic");
}
}
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.MediaType
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody
@WebFluxTest(UserVehicleController::class)
class MyControllerTests(@Autowired val webClient: WebTestClient) {
@MockBean
lateinit var userVehicleService: UserVehicleService
@Test
fun testExample() {
given(userVehicleService.getVehicleDetails("sboot"))
.willReturn(VehicleDetails("Honda", "Civic"))
webClient.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN).exchange()
.expectStatus().isOk
.expectBody<String>().isEqualTo("Honda Civic")
}
}
WebTestClient
in a simulated web application currently only works in WebFlux.
@WebFluxTest
annotation cannot detect routes registered with the web functional framework. To test
RouterFunction
beans in context, consider importing
RouterFunction
yourself using the
@Import
annotation or using the
@SpringBootTest
annotation .
@WebFluxTest
annotation cannot detect a custom security configuration registered as a
@Bean
type
SecurityWebFilterChain
. To include it in your test, you need to import the configuration that registers the bean using the
@Import
annotation or the
@SpringBootTest
annotation.
GO TO FULL VERSION