Prueba el resorte El marco de prueba MVC, también conocido como MockMvc, proporciona soporte para probar aplicaciones Spring MVC. Realiza el procesamiento completo de solicitudes Spring MVC a través de objetos simulados de solicitud y respuesta en lugar de un servidor en ejecución.
MockMvc se puede usar por separado para realizar solicitudes
y validar respuestas. También se puede utilizar a través de WebTestClient,
donde MockMvc está conectado como servidor para procesar solicitudes. La ventaja de WebTestClient
es la
capacidad de trabajar con objetos de nivel superior en lugar de datos sin procesar, y la capacidad de pasar a
pruebas HTTP completas de un extremo a otro en un servidor real utilizando la misma API de prueba.
Breve descripción
Puedes escribir pruebas unitarias simples para Spring MVC creando una instancia de un
controlador, agregándole dependencias y llamando a sus métodos. Sin embargo, dichas pruebas no verifican las
asignaciones de solicitudes, el enlace de datos, la conversión de mensajes, la conversión de tipos, la validación y
no utilizan ninguno de los métodos auxiliares anotados con @InitBinder
, @ModelAttribute
,
o @ExceptionHandler
.
El marco de prueba Spring MVC, también conocido como
MockMvc
, tiene como objetivo proporcionar pruebas más completas de los controladores Spring MVC sin un
servidor en ejecución. Para ello, llama a DispatcherServlet
y pasa implementaciones
simuladas de API de servlet desde el módulo spring-test
, que reproduce el procesamiento
completo de las solicitudes Spring MVC sin un servidor en ejecución.
MockMvc es un marco de prueba del lado del servidor que le permite probar la mayoría de los Funcionalidad de una aplicación Spring MVC que utiliza pruebas ligeras y específicas. Puede usarlo por separado para realizar solicitudes y verificar respuestas, o puede usarlo a través de la API WebTestClient con MockMvc conectado como servidor para procesar solicitudes.
Importación estática
Cuando utilice MockMvc directamente para ejecutar solicitudes, deberá realizar una importación estática:
MockMvcBuilders.*
MockMvcRequestBuilders.*
MockMvcResultMatchers.*
MockMvcResultHandlers.*
Una manera fácil de recordarlos es buscar MockMvc*
. Si está utilizando Eclipse, asegúrese de
agregar también los miembros estáticos anteriores a sus "favoritos" en las preferencias de Eclipse.
Cuando
utilice MockMvc a través de WebTestClient, no necesitará importación estática. WebTestClient
proporciona una API fluida sin importación estática.
Opciones de configuración
MockMvc puede ser configurado de dos maneras. Una es apuntar directamente a los controladores que deben probarse y configurar mediante programación el marco Spring MVC. El segundo es señalar la configuración de Spring cuando se utiliza el marco Spring MVC y el marco del controlador dentro de él.
Para configurar MockMvc para probar un controlador específico, use lo siguiente:
class MyWebTests {
MockMvc mockMvc;
@BeforeEach
void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
}
// ...
}
class MyWebTests {
lateinit var mockMvc : MockMvc
@BeforeEach
fun setup() {
mockMvc = MockMvcBuilders.standaloneSetup(AccountController()).build()
}
// ...
}
O también puedes usar esta configuración al realizar pruebas a través de WebTestClient, que delega autoridad a la misma herramienta de compilación, como se muestra arriba.
Para configurar MockMvc a través de la configuración de Spring, use lo siguiente:
@SpringJUnitWebConfig(locations = "my-servlet-context.xml")
class MyWebTests {
MockMvc mockMvc;
@BeforeEach
void setup(WebApplicationContext wac) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
// ...
}
@SpringJUnitWebConfig(locations = ["my-servlet-context.xml"])
class MyWebTests {
lateinit var mockMvc: MockMvc
@BeforeEach
fun setup(wac: WebApplicationContext) {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
}
// ...
}
O también puede utilizar esta configuración al realizar pruebas a través de WebTestClient, que delega autoridad a la misma herramienta de compilación que se muestra arriba.
¿Qué opción de configuración debería utilizar?
webAppContextSetup
carga la configuración real de Spring MVC, lo que permite una prueba de integración más completa. Debido a que el
marco TestContext almacena en caché la configuración de Spring cargada, le ayuda a ejecutar pruebas rápidamente
incluso cuando introduce más pruebas en su conjunto de pruebas. Además, puede inyectar servicios simulados en los
controladores utilizando la configuración de Spring para centrarse en las pruebas de nivel web. En el siguiente
ejemplo, el servicio simulado se declara usando Mockito:
<bean id="accountService" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.example.AccountService"/>
</bean>
Luego puede inyectar el servicio simulado en una prueba para configurar y probar los eventos esperados, como se muestra en el siguiente ejemplo:
@SpringJUnitWebConfig(locations = "test-servlet-context.xml")
class AccountTests {
@Autowired
AccountService accountService;
MockMvc mockMvc;
@BeforeEach
void setup(WebApplicationContext wac) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
// ...
}
@SpringJUnitWebConfig(locations = ["test-servlet-context.xml"])
class AccountTests {
@Autowired
lateinit var accountService: AccountService
lateinit mockMvc: MockMvc
@BeforeEach
fun setup(wac: WebApplicationContext) {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
}
// ...
}
La configuración standaloneSetup
, por otro lado, es un poco más adecuada para pruebas unitarias.
Ella prueba un controlador a la vez. Es posible inyectar manualmente dependencias simuladas en el controlador sin
necesidad de cargar la configuración de Spring. Estas pruebas están más orientadas al estilo y le permiten
comprender qué controlador se está probando, si se requiere alguna configuración Spring MVC específica para
funcionar, etc. Configurar standaloneSetup
también es una forma muy conveniente de escribir pruebas
personalizadas para probar una lógica específica o depurar un problema.
Como ocurre con la mayoría de los
debates sobre pruebas de integración versus pruebas unitarias, existen No hay respuesta correcta o incorrecta. Sin
embargo, usar standaloneSetup
implica la necesidad de realizar pruebas adicionales usando webAppContextSetup
para validar la configuración de Spring MVC. Además, puede escribir todas sus pruebas usando webAppContextSetup
para probar siempre con una configuración real de Spring MVC.
Funciones de configuración
No importa
qué herramienta Ensamblajes de MockMvc que utilice, todas las implementaciones de MockMvcBuilder
proporcionarán algunas funciones comunes y extremadamente útiles. Por ejemplo, podría declarar un encabezado Aceptar
en todas las solicitudes y aceptar el estado 200, así como un encabezado Content-Type
en todas las
respuestas, como se muestra a continuación:
// importación estática MockMvcBuilders.standaloneSetup
MockMvc mockMvc = standaloneSetup(new MusicController())
.defaultRequest(get("/").accept(MediaType.APPLICATION_JSON))
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType("application/json;charset=UTF-8"))
.build();
// No es posible en
Kotlin hasta está arreglado
Además, los marcos (y aplicaciones) de terceros pueden empaquetar previamente instrucciones de configuración, por
ejemplo en MockMvcConfigurer
. Spring Framework tiene una implementación incorporada que ayuda a
guardar y reutilizar la sesión HTTP en todas las solicitudes. Puedes usarlo así:
// importación estática SharedHttpSessionConfigurer.sharedHttpSession
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TestController())
.apply(sharedHttpSession())
.build();
// Importa mockMvc para ejecutar consultas...
// No es posible en Kotlin hasta no se solucionará
Ver. javadoc por ConfigurableMockMvcBuilder
para obtener una lista de todas las capacidades
del generador MockMvc, o use el IDE para explorar las opciones disponibles.
Ejecución de consultas
En esta sección se muestra cómo usar MockMvc por separado para realizar solicitudes y validar respuestas. Si está
utilizando MockMvc a través de WebTestClient
, consulte la sección correspondiente en
escribiendo pruebas.
Para ejecutar solicitudes utilizando cualquier método HTTP, confíe en el ejemplo que se muestra a continuación:
// importación estática MockMvcRequestBuilders.*.
mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON));
import org.springframework.test.web.servlet.post
mockMvc.post("/hotels/{id}", 42) {
accept = MediaType.APPLICATION_JSON
}
También es posible realizar solicitudes de carga de archivos que utilicen internamente MockMultipartHttpServletRequest
para evitar analizar la solicitud de varias partes. Más bien, tendrás que configurarlo como se muestra en el
siguiente ejemplo:
mockMvc.perform(multipart("/doc").file("a1 " , "ABC".getBytes("UTF-8")));
import org.springframework.test.web.servlet.multipart
mockMvc.multipart("/doc") {
file("a1", "ABC".toByteArray(charset("UTF8")))
}
Puede especificar parámetros de solicitud en el estilo de patrón URI, como se muestra en el siguiente ejemplo:
mockMvc.perform(get ("/hotels?thing={thing}", "somewhere"));
mockMvc .get("/hotels?thing={thing}", "somewhere")
También puede agregar parámetros de solicitud de servlet, que son solicitudes parámetros, o parámetros de formulario, como se muestra en el siguiente ejemplo:
mockMvc.perform(get("/hotels").param("thing", " somewhere"));
import org.springframework.test.web.servlet.get
mockMvc.get("/hotels") {
param("thing", "somewhere")
}
Si el código de la aplicación utiliza parámetros de solicitud de servlet y no verifique la cadena de consulta
explícitamente (que es lo que sucede con mayor frecuencia), entonces no importa qué opción use. Sin embargo,
tenga en cuenta que los parámetros de solicitud proporcionados mediante el patrón URI se decodifican, mientras
que se espera que los parámetros de solicitud proporcionados mediante el método param(...)
ya estén
decodificados.
En en la mayoría de los casos, es preferible omitir la ruta de contexto y la ruta del servlet del URI de
solicitud. Si necesita realizar la prueba con el URI de solicitud completo, asegúrese de configurar contextPath
y servletPath
de manera adecuada para que la coincidencia de solicitudes funcione, como se muestra
en el siguiente ejemplo:
mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main"))
import org.springframework.test.web.servlet.get
mockMvc.get("/ app/main/hotels/{id}") {
contextPath = "/app"
servletPath = "/main"
}
En el ejemplo anterior, No será conveniente configurar contextPath
y servletPath
en
cada solicitud realizada. En su lugar, puede establecer propiedades de consulta predeterminadas, como se muestra
en el siguiente ejemplo:
class MyWebTests {
MockMvc mockMvc;
@BeforeEach
void setup() {
mockMvc = standaloneSetup(new AccountController())
.defaultRequest(get("/")
.contextPath("/app").servletPath("/main")
.accept(MediaType.APPLICATION_JSON)).build();
}
}
// No es posible en Kotlin hasta no se solucionará
Las propiedades anteriores afectan a cada solicitud realizada a través del instancia MockMvc
. Si
también se especifica la misma propiedad en una solicitud determinada, anula el valor predeterminado. Por lo
tanto, el método HTTP predeterminado y el URI en la solicitud son irrelevantes porque deben especificarse para
cada solicitud.
Definición de eventos esperados
Puede definir eventos esperados agregando una o varias llamadas a andExpect(..)
después de que se
haya completado la solicitud, como se muestra en el siguiente ejemplo. Si algún evento esperado no ocurre, otros
eventos esperados no se confirmarán.
// importación estática MockMvcRequestBuilders.*. y MockMvcResultMatchers.*
mockMvc.perform(get("/accounts/1")).andExpect(status().isOk());
import org.springframework.test.web.servlet.get
mockMvc.get("/accounts/1").andExpect {
status { isOk() }
}
Puede definir varios eventos esperados agregando andExpectAll(..)
después de ejecutar la solicitud,
como se muestra en el siguiente ejemplo. A diferencia de andExpect(..)
,
andExpectAll(..)
garantiza que todos los eventos esperados especificados se confirmen y que
cualquier evento fallido se supervise y se informe.
// importación estática MockMvcRequestBuilders.*. y MockMvcResultMatchers.*
mockMvc.perform(get("/accounts/1")).andExpectAll(
status().isOk(),
content().contentType("application/json;charset=UTF-8"));;
MockMvcResultMatchers.*
especifica una serie de eventos esperados, algunos de los cuales están
anidados dentro de eventos esperados más detallados.
Los eventos esperados se dividen en dos categorías generales. La primera categoría de aserciones verifica las propiedades de la respuesta (como el estado de la respuesta, los encabezados y el contenido). Estos son los resultados más importantes que se pueden respaldar.
La segunda categoría de afirmaciones va más allá del alcance de la respuesta. Estas afirmaciones le permiten verificar aspectos específicos de Spring MVC, como qué método de controlador manejó la solicitud, si se lanzó y manejó una excepción, cuál es el contenido del modelo, qué vista se seleccionó, qué atributos flash se agregaron, etc. en. También le permiten probar aspectos específicos de los servlets, como los atributos de solicitud y sesión.
La siguiente prueba confirma que el enlace o la validación fallaron:
mockMvc.perform(post("/persons"))
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("person"));
import org.springframework.test.web.servlet.post
mockMvc.post("/persons").andExpect {
status { isOk() }
model {
attributeHasErrors("person")
}
}
A menudo, al escribir pruebas, es útil descargar los resultados de la consulta ejecutada. Esto se puede hacer
de la siguiente manera, donde print()
es un elemento estático importado de
MockMvcResultHandlers
:
mockMvc.perform(post("/persons"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("person"));
import org.springframework.test.web.servlet.post
mockMvc.post("/persons").andDo {
print()
}.andExpect {
status { isOk() }
model {
attributeHasErrors("person")
}
}
Siempre que el La solicitud se procesa no generará una excepción no controlada, el método print()
generará todos los datos de resultados disponibles en System.out
. También hay un método
log()
y dos variantes adicionales del método print()
, uno de los cuales toma un OutputStream
y el otro toma un
Escritor
. Por ejemplo, llamar a print(System.err)
genera los datos del resultado en System.err
y
llamar a print(myWriter)
genera los datos del resultado. a un método especial de registros (escritor).
Si desea que los datos del resultado se registren en lugar de imprimirse, puede llamar al método log()
,
que escribe los datos del resultado como un único mensaje DEBUG
en la categoría de registro. org.springframework.test.web.servlet.result
.
En algunos casos, es posible que desee acceder directamente al resultado y probar algo que no se puede probar de
ninguna otra manera. Esto se puede lograr agregando .andReturn()
después de todos los demás eventos
esperados, como se muestra en el siguiente ejemplo:
MvcResult mvcResult = mockMvc.perform(post("/personas")).andExpect(status().isOk()).andReturn();
// ...
var mvcResult = mockMvc.post("/persons").andExpect { status { isOk() } }.andReturn()
// ...
Si todas las pruebas repiten los mismos eventos esperados, entonces puede establecer valores comunes eventos
esperados una vez al crear una instancia de MockMvc
, como se muestra en el siguiente ejemplo:
standaloneSetup(new SimpleController())
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType("application/json;charset=UTF-8"))
.build()
// No es posible en Kotlin hasta
que se solucione
Tenga en cuenta que los eventos esperados genéricos siempre se aplican y no se pueden anular sin crear una
instancia separada de MockMvc
.
Si el contenido de la respuesta JSON contiene enlaces hipermedia creados usando Spring HATEOAS, puede validar los enlaces resultantes utilizan expresiones JsonPath, como se muestra en el siguiente ejemplo:
mockMvc.perform(get("/people").accept (MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people"));
mockMvc.get("/people") {
accept(MediaType.APPLICATION_JSON)
}.andExpect {
jsonPath("$.links[?(@.rel == 'self')].href") {
value("http://localhost:8080/people")
}
}
Si el contenido de la respuesta XML contiene enlaces hipermedia creados usando Spring HATEOAS , usted Puede comprobar los enlaces recibidos utilizando expresiones XPath:
Map<String, String> ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom");
mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML))
.andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people"));
val ns = mapOf("ns" to "http://www.w3.org/2005/Atom")
mockMvc.get("/handle") {
accept(MediaType.APPLICATION_XML)
}.andExpect {
xpath("/person/ns:link[@rel='self']/@href", ns) {
string("http://localhost:8080/people")
}
}
Asincrónico request
Esta sección muestra cómo usar MockMvc por separado para probar el procesamiento de solicitudes asincrónicas. Cuando se utiliza MockMvc a través de WebTestClient, no hay nada especial en hacer que funcionen las solicitudes asincrónicas, ya que WebTestClient hace automáticamente lo que se describe en esta sección.
Async Servlet 3.0 Las solicitudes, compatibles con Spring MVC, funcionan saliendo del subproceso del contenedor de servlets, por lo que permitiendo que la aplicación evalúe la respuesta de forma asincrónica, después de lo cual se realiza el envío asincrónico para completar el procesamiento en el subproceso del contenedor de servlet.
En Spring MVC Test, las solicitudes asincrónicas se
pueden probar afirmando primero el valor asincrónico generado y luego realizar manualmente el envío asincrónico y
finalmente verificar la respuesta. A continuación se muestra una prueba de ejemplo para métodos de controlador que
devuelven DeferredResult
, Callable
o un tipo reactivo como Mono
de Reactor:
// importación estática MockMvcRequestBuilders.*. y MockMvcResultMatchers.*
@Test
void test() throws Exception {
MvcResult mvcResult = this.mockMvc.perform(get("/path"))
.andExpect(status().isOk())
.andExpect(request().asyncStarted())
.andExpect(request() .asyncResult("body"))
.andReturn();
this.mockMvc.perform(asyncDispatch(mvcResult))
.andExpect(status().isOk())
.andExpect(content().string("body")); }
- El estado de verificación de respuesta permanece sin cambios
- El procesamiento asincrónico debe comenzar
- Esperamos y confirmamos el resultado del procesamiento asincrónico
- Realizamos manualmente el envío asincrónico (ya que no hay ningún contenedor en ejecución)
- Verificamos la respuesta final
@Test
fun test() {
var mvcResult = mockMvc.get("/path").andExpect {
status { isOk() }
request { asyncStarted() }
// TODO Eliminar genérico no utilizado solicitud de parámetro request { asyncResult<Nothing> ("body") }
}.andReturn()
mockMvc.perform(asyncDispatch(mvcResult))
.andExpect {
status { isOk() }
content().string("body ")
}
}
- El estado de verificación de respuesta permanece sin cambios
- Procesamiento asincrónico
- Esperamos y confirmamos el resultado del procesamiento asincrónico
- Realizamos manualmente el envío asincrónico (ya que no hay ningún contenedor en ejecución)
- Verificar la respuesta final
Respuestas de transmisión
La mejor manera de probar respuestas de transmisión, como eventos enviados por el servidor, es WebTestClient, que
se puede utilizar como cliente de prueba para conectarse. un instancia
MockMvc
para ejecutar pruebas en controladores Spring MVC sin un servidor en ejecución. Por ejemplo:
WebTestClient client = MockMvcWebTestClient.bindToController(new SseController()).build();
FluxExchangeResult<Person> exchangeResult = client.get()
.uri("/persons")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType("text/event-stream")
.returnResult(Person.class);
// Utilice StepVerifier de Project Reactor para probar la respuesta de transmisión
StepVerifier.create(exchangeResult.getResponseBody())
.expectNext(new Person("N0"), new Person("N1"), new Person("N2"))
.expectNextCount(4)
.consumeNextWith(person -> assertThat(person.getName()).endsWith("7"))
.thenCancel()
.verify();
WebTestClient
también puede conectarse a un servidor en vivo y ejecutar pruebas de integración
completas de un extremo a otro. Esto también es compatible con Spring Boot, donde puede Probar un servidor en ejecución.
Registro de filtros
Al configurar una instancia MockMvc
puede registrar una o más instancias de servlets
Filter
, como se muestra en el siguiente ejemplo:
mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build();
// No es posible en Kotlin hasta que se solucione
Los filtros registrados se llaman a través de MockFilterChain
desde
spring-test
, y el último filtro se delega a DispatcherServlet
.
MockMvc frente a pruebas de extremo a extremo
MockMvc se basa en implementaciones simuladas de API de servlet del módulo spring-test
y está
independiente del contenedor en ejecución. Por lo tanto, existen algunas diferencias en comparación con las
pruebas de integración completas de un extremo a otro con un cliente real y un servidor real.
La forma más sencilla de entender esto es comenzar con un MockHttpServletRequest
vacío
solicitud. Todo lo que le agregues se convierte en una solicitud. Cosas que pueden sorprenderle: no existe
una ruta de contexto predeterminada; sin cookie jsessionid
; no hay redirecciones, errores ni
envío asincrónico; y por lo tanto prácticamente no hay renderizado (visualización) JSP. En cambio, las URL
"redireccionadas" y "reenviadas" se almacenan en MockHttpServletResponse
, después de lo cual se
pueden validar con los eventos esperados.
Esto significa que si se utiliza JSP, entonces JSP puede ser validado: la página a la que se redirigió la
solicitud, pero el HTML no se representará. En otras palabras, no se llama a JSP. Sin embargo, tenga en
cuenta que cualquier otra tecnología de representación que no dependa de la redirección, como Thymeleaf y
Freemarker, pasa HTML al cuerpo de la respuesta como se esperaba. Lo mismo se aplica a la representación de
JSON, XML y otros formatos utilizando métodos anotados con @ResponseBody
.
Como alternativa, es posible que desees considerar el soporte completo de Spring Boot para aplicaciones de
extremo a extremo. Finalizar las pruebas de integración con anotaciones @SpringBootTest
.
Consulte " Primavera Guía de referencia de arranque.
Cada enfoque tiene sus ventajas y desventajas. Las opciones proporcionadas en Spring MVC Test se encuentran
en diferentes lugares de la escala, desde las pruebas unitarias clásicas hasta las pruebas de integración
completa. Por supuesto, ninguna de las opciones de Spring MVC Test entra en la categoría de pruebas
unitarias clásicas, pero están un poco más cerca de ella. Por ejemplo, puede aislar el nivel web inyectando
servicios simulados en los controladores, en cuyo caso el nivel web solo se probará a través de DispatcherServlet
,
pero con la configuración de Spring real, ya que puede probar el acceso a los datos. nivel por separado de
los niveles superiores. Alternativamente, puede usar una configuración independiente enfocándose en un
controlador a la vez y proporcionando manualmente la configuración necesaria para que funcione.
Otra diferencia importante al usar una prueba en el marco de prueba Spring MVC es que: Conceptualmente, las pruebas son del lado del servidor, por lo que tiene la capacidad de verificar qué identificador se usó, si la excepción fue manejada por HandlerExceptionResolver, cuál era el contenido del modelo, qué errores de enlace hubo y otros detalles. Esto significa que resulta más fácil escribir eventos esperados, ya que el servidor no es una caja opaca, como es el caso cuando se prueba a través de un cliente HTTP real. En general, la ventaja de las pruebas unitarias clásicas es que: dichas pruebas son más fáciles de escribir, justificar y depurar, pero no reemplazan la necesidad de pruebas de integración completas. Al mismo tiempo, es importante no perder de vista que la respuesta es lo más importante a comprobar. En resumen, hay espacio para múltiples estilos y estrategias de prueba, incluso dentro del mismo proyecto.
Ejemplos adicionales
Las pruebas propias del marco incluyen muchos casos de prueba llamado muestra cómo usar MockMvc solo o mediante WebTestClientConsulte estos ejemplos para obtener más ideas.