Fans simulados y espías

Al ejecutar pruebas, a veces es necesario simular ciertos componentes en el contexto de la aplicación. Por ejemplo, es posible que tenga una fachada para algún servicio remoto al que no se pueda acceder durante el desarrollo. La burla también puede ser útil si necesita simular fallas que son difíciles de causar en un entorno real.

Spring Boot contiene una anotación @MockBean que se puede usar para definir un Mockito. simulacro de un bean dentro de ApplicationContext. Puede utilizar una anotación para agregar nuevos beans o reemplazar una definición de bean existente. La anotación se puede utilizar directamente en clases de prueba, en campos dentro de una prueba o en clases y campos con la anotación @Configuration. Cuando se usa con un campo, también se inyecta una instancia del objeto simulado creado. Los simulados beans se restablecen automáticamente después de ejecutar cada método de prueba.

Si la prueba utiliza una de las anotaciones de prueba de Spring Boot (por ejemplo, @ SpringBootTest), esta característica se habilita automáticamente. Para utilizar esta característica con otro tipo de organización, debe agregar explícitamente oyentes, como se muestra en el siguiente ejemplo:

Java

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 {
    // ...
}
Kotlin

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 {
    // ...
}

El siguiente ejemplo reemplaza un bean RemoteService existente con una implementación simulada:

Java

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");
    }
}
Kotlin

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")
    }
}
-La anotación @MockBean no se puede utilizar para Lógica simulada: el bean que se ejecuta cuando se actualiza el contexto de la aplicación. Para cuando se ejecute la prueba, la actualización del contexto de la aplicación ya se habrá completado y será demasiado tarde para configurar la lógica operativa simulada. En esta situación, recomendamos utilizar un método marcado con la anotación @Bean para crear y configurar el objeto simulado.

Alternativamente, puede utilizar anotación @SpyBean para envolver cualquier bean existente con un objeto spy de Mockito.

Proxies de CGLib, como los creados para los beans incluidos en el ámbito de accesibilidad, declare los métodos proxy como final. Esto impide que el marco Mockito funcione correctamente porque no puede simular ni seguir los métodos final en su configuración predeterminada. Si necesita simular o monitorear dicho bean, configure Mockito para usar un constructor de objetos simulados en línea agregando org.mockito:mockito-inline a las dependencias de prueba de su aplicación. Esto permite a Mockito burlarse y seguir los métodos final.
Aunque el marco de pruebas de Spring almacena en caché los contextos de las aplicaciones entre pruebas y reutiliza los contexto para pruebas con la misma configuración, el uso de las anotaciones @MockBean o @SpyBean afecta la clave de caché, lo que probablemente aumentará la cantidad de contextos.
Si usa la anotación @SpyBean para realizar un seguimiento de los beans con métodos anotados @Cacheable que hacen referencia a parámetros por nombre , su aplicación debe compilarse con la opción -parameters. De esta manera, se garantiza que la infraestructura de almacenamiento en caché tendrá acceso a los nombres de los parámetros una vez que se descubra el bean.
Si la anotación es @SpyBean se usa para monitorear el bean que está siendo proxy en Spring, en algunas situaciones puede ser necesario eliminar el proxy de Spring, por ejemplo, al configurar eventos esperados usando given o when. Esto se hace usando AopTestUtils.getTargetObject(yourProxiedSpy).

Pruebas de configuración automática

El sistema de configuración automática de Spring Boot funciona muy bien con las aplicaciones, pero a veces puede ser demasiado redundante para las pruebas. A menudo resulta práctico cargar sólo aquellas partes de la configuración que son necesarias para probar la "capa" de la aplicación. Por ejemplo, es posible que desee asegurarse de que los controladores Spring MVC representen las URL correctamente, pero no desee involucrar llamadas a bases de datos en esas pruebas, o tal vez desee probar entidades JPA, pero el nivel web no está interesado en ejecutar esas pruebas. . .

El módulo spring-boot-test-autoconfigure contiene una serie de anotaciones que se pueden utilizar para configurar automáticamente dichas "capas". Cada uno de ellos funciona de la misma manera, representando una anotación del formulario @...​Test, que carga ApplicationContext, y una o más anotaciones del formulario @AutoConfigure...​ , que se puede utilizar para configurar parámetros de configuración automática.

Cada capa restringe el escaneo de componentes al área relevante componentes y carga un conjunto muy limitado de clases de configuración automática. Si necesita excluir uno de ellos, la mayoría de las anotaciones del formulario @...​Test proporcionan un atributo excludeAutoConfiguration. Como alternativa, puede utilizar la anotación @ImportAutoConfiguration#exclude.
Habilitar múltiples “capas” usando múltiples anotaciones de la forma @…​Test en una prueba no es compatible. Si se requieren varias "capas", seleccione una de las anotaciones @...​Test y agregue las anotaciones @AutoConfigure...​ a las otras "capas" manualmente.
También puedes usar anotaciones como @AutoConfigure...​ con la anotación estándar @SpringBootTest. Puede utilizar esta combinación si no está interesado en crear capas de su aplicación, pero necesita algunos de los beans de prueba configurados automáticamente.

Pruebas JSON configuradas automáticamente

Para garantizar Para que los objetos de serialización y deserialización JSON funcionen como se esperaba, puede utilizar la anotación @JsonTest. La anotación @JsonTest configura automáticamente un asignador JSON compatible disponible, que puede ser una de las siguientes bibliotecas:

  • ObjectMapper de Jackson, cualquier beans con la anotación @JsonComponent y cualquier Modules de Jackson

  • Gson

  • Jsonb

La lista de configuraciones automáticas activadas por la anotación @JsonTest se puede encontrar en el apéndice de documentación.

Si necesita configurar elementos de configuración automática, puede utilizar la anotación @AutoConfigureJsonTesters.

Spring Boot contiene clases auxiliares basadas en AssertJ que funcionan con JSONAssert y bibliotecas JsonPath, diseñadas para comprobar que la asignación JSON prevista es correcta. Las clases JacksonTester, GsonTester, JsonbTester y BasicJsonTester se pueden utilizar para las bibliotecas Jackson, Gson, Jsonb y Strings. , respectivamente. Cualquier campo auxiliar de la clase de prueba se puede marcar con la anotación @Autowired cuando se utiliza la anotación @JsonTest. El siguiente ejemplo muestra una clase de prueba para la biblioteca Jackson:

Java

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");
        // Aserción para un archivo ".json" file in the same package as the test
        assertThat(this.json.write(details)).isEqualToJson("expected.json");
        // O usar aserciones basadas en la ruta JSON
        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");
    }
}
Kotlin

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")
        // Aserción para un archivo ".json" en el mismo paquete que la prueba
        assertThat(json.write(details)).isEqualToJson("expected.json")
        // O use aserciones basadas en la ruta JSON
        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")
    }
}
Las clases auxiliares JSON también se pueden usar directamente en pruebas unitarias estándar. Para hacer esto, llame al método initFields de la clase auxiliar en su método anotado con @Before si no se utiliza la anotación @JsonTest.

Si usa las clases auxiliares Spring Boot basadas en AssertJ para agregar una aserción para un valor numérico en una ruta JSON determinada, es posible que no necesite usar isEqualTo dependiendo del tipo. En su lugar, puede utilizar la función satisfies en AssertJ para agregar una afirmación de que un valor satisface una condición determinada. Por ejemplo, el siguiente ejemplo agrega una declaración de que el número real es un valor flotante cercano a 0.15 dentro del desplazamiento 0.01.

Java

@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)));
}
Kotlin

@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))
        })
}

Pruebas autoconfigurables de Spring MVC

Para probar los controladores Spring MVC para la funcionalidad prevista, utilice @WebMvcTest anotación. La anotación @WebMvcTest configura automáticamente el marco Spring MVC y limita los beans escaneados a @Controller, @ControllerAdvice, @JsonComponent, Convertidor, GenericConverter, Filtro, HandlerInterceptor, WebMvcConfigurer, WebMvcRegistrations y HandlerMethodArgumentResolver. Los beans normales con anotaciones @Component y @ConfigurationProperties no se analizan cuando se utiliza la anotación @WebMvcTest. Para agregar beans marcados con la anotación @ConfigurationProperties, puede usar la anotación @EnableConfigurationProperties.

Si necesita registrar componentes adicionales, como Module de Jackson, puede importar clases de configuración adicionales utilizando la anotación @Import en su prueba.

A menudo, la anotación @WebMvcTest se limita a un único controlador y se usa junto con la anotación @MockBean para pasar implementaciones simuladas para los objetos de comunicación requeridos.

@WebMvcTest también configura automáticamente MockMvc. Mock MVC proporciona una manera eficiente de probar rápidamente controladores MVC sin tener que ejecutar un servidor HTTP completo.

También puede configurar automáticamente MockMvc sin la anotación @WebMvcTest (y por ejemplo, con la anotación @SpringBootTest), anotándolo con @AutoConfigureMockMvc. El siguiente ejemplo utiliza MockMvc:
Java

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"));
    }
}
Kotlin

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"))
    }
}
Si necesita configurar elementos de configuración automática (por ejemplo, si necesita aplicar filtros de servlet), puede usar los atributos en el Anotación @AutoConfigureMockMvc.

Si está utilizando HtmlUnit y Selenium, la configuración automática también proporciona un bean WebClient para HtmlUnit y/o un WebDriver frijol para selenio. El siguiente ejemplo utiliza HtmlUnit:

Java

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");
    }
}
Kotlin

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")
    }
}
De forma predeterminada, Spring Boots coloca WebDriver en un "área de disponibilidad" especial para garantizar que el controlador salga después de cada prueba y se implemente una nueva instancia. Si esta lógica operativa es innecesaria, puede agregar la anotación @Scope("singleton") a la definición de su WebDriver con el @Bean annotation.
Un alcance de accesibilidad webDriver creado por Spring Boot reemplazará cualquier alcance de accesibilidad definido por el usuario con el mismo nombre. Si tiene su propio alcance de accesibilidad webDriver definido, es posible que deje de funcionar cuando utilice la anotación @WebMvcTest.

Si el classpath tiene la anotación Spring Security @WebMvcTest también escaneará los beans WebSecurityConfigurer. En lugar de deshabilitar completamente la seguridad para dichas pruebas, puede utilizar el soporte de pruebas de Spring Security.

A veces, escribir pruebas en Spring MVC no es suficiente; Spring Boot puede ayudarlo a ejecutar pruebas de un extremo a otro con todas las funciones utilizando un servidor real.

Pruebas de autoconfiguración de Spring WebFlux

Para garantizar que los controladores Spring WebFlux funcionen como se espera, puede utilizar la anotación @WebFluxTest. La anotación @WebFluxTest configura automáticamente el marco Spring WebFlux y limita los beans escaneados a @Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, WebFilter y WebFluxConfigurer. Los beans normales con anotaciones @Component y @ConfigurationProperties no se escanean cuando se utiliza la anotación @WebFluxTest. Para agregar beans marcados con la anotación @ConfigurationProperties, puede usar la anotación @EnableConfigurationProperties.

Si necesita registrar componentes adicionales como Module de la biblioteca Jackson, puede importar clases de configuración adicionales utilizando la anotación @Import en la prueba.

A menudo, la anotación de @WebFluxTest se limita a un único controlador y se utiliza junto con la anotación @MockBean para pasar implementaciones simuladas para los objetos de comunicación requeridos.

El código de anotación @WebFluxTest> también configura automáticamente WebTestClient, lo que proporciona un medio eficiente para probar rápidamente los controladores WebFlux sin la necesidad de ejecutar un servidor HTTP completo.

También puede configurar automáticamente WebTestClient sin la anotación @WebFluxTest (y por ejemplo con la anotación @SpringBootTest) anotándola con @AutoConfigureWebTestClient. El siguiente ejemplo muestra una clase que utiliza las anotaciones @WebFluxTest y WebTestClient:
Java

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");
    }
}
Kotlin

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")
    }
}
Esta configuración solo es compatible con aplicaciones WebFlux, ya que el uso de WebTestClient en una aplicación web simulada actualmente solo funciona en WebFlux.
La anotación @WebFluxTest no puede detectar rutas registradas con el marco funcional web. Para probar los beans RouterFunction en contexto, considere importar RouterFunction usted mismo usando la anotación @Import o usando la anotación @SpringBootTest .
La anotación @WebFluxTest no puede detectar una configuración de seguridad personalizada registrada como @Bean escriba SecurityWebFilterChain. Para incluirlo en su prueba, debe importar la configuración que registra el bean usando la anotación @Import o @SpringBootTest annotation.
A veces escribir pruebas en Spring WebFlux no es suficiente; Spring Boot puede ayudarlo a ejecutar pruebas de un extremo a otro con todas las funciones utilizando un servidor real.