MockMvc – Introducción a Spring MVC testing framework: Probando endpoints
Introducción
En esta publicación veremos cómo utilizar MockMvc para probar endpoints creados con Spring. Este es el primer post de una serie dónde analizaremos las principales ventajas de utilizar Spring MVC test framework para probar clases de tipo Controller respecto a otras soluciones y cuáles son las distintas formas de configurar los tests.
El código fuente completo del artículo puede encontrarse en GitHub. El proyecto contiene un controller MVC estándar que reenvía las llamadas a un recurso estático y dos controladores REST que devuelven una lista de lenguajes y otra de cafés.
MockMvc y la pirámide de los tests

En su libro, Succeeding with Agile: Software Development Using Scrum, Mike Cohn describe la pirámide de automatización de los tests como una estrategia de automatización de tests en términos de cantidad y esfuerzo que deben dedicarse a cada uno de los tipos de test. La pirámide también la emplean otros autores para definir un portfolio de tests en base al coste y velocidad. La base de la pirámide son los tests unitarios, por lo que debemos entender que los tests unitarios son los cimientos de la estrategia de testing y que debería de haber muchos más tests unitarios que tests end-to-end de alto nivel.
Como ya sabrás, los test unitarios se centran en probar el componente o unidad más pequeño de un software. En la programación orientada a objetos esto se traduce normalmente un una sola clase o interfaz, o un método o función dentro de esta clase. El test debe de realizarse de forma aislada a otras unidades del mismo software, para conseguir esto normalmente las dependencias de la unidad a probar se simulan (mock). Una de las características principales de los tests unitarios es que su ejecución debe de ser realmente rápida.
De forma similar, los tests de integración se centran en probar una combinación de las unidades o componentes anteriormente mencionados. Estrictamente hablando, los tests utilizando MockMvc
se encuentran en la frontera entre los tests unitarios y lost tests de integración. No son tests unitarios porque los endpoints se prueban de forma integrada con un contenedor MVC con dependencias e inputs simulados. Pero tampoco podríamos considerar estos test como de integración, ya que si se definen correctamente, con MockMvc
sólo probaremos un único componente con una plataforma simulada, pero no una combinación de componentes.
Tecnicismos a un lado, cuando preparemos nuestro portfolio de tests para un proyecto, los tests empleando Spring MVC deberían de tratarse como tests unitarios y ser parte de los cimientos de nuestra estrategia de testing.
Controller de lenguajes
Para ilustrar los ejemplos de este tutorial vamos a crear un controller sencillo que devuelve una lista de lenguajes de programación.
1@Controller
2@RequestMapping(value = "/api", produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
3public class LanguageApiController {
4
5 private final LanguageService languageService;
6
7 @Autowired
8 public LanguageApiController(
9 LanguageService languageService) {
10 this.languageService = languageService;
11 }
12
13 @RequestMapping(value = "/languages", method = GET)
14 public ResponseEntity<List<Language>> getLanguages(@RequestParam(value = "contains", required = false) String contains) {
15 return ResponseEntity.ok(languageService.getLanguages(contains));
16 }
17
18 @RequestMapping(value = "/languages/{name}", method = GET)
19 public ResponseEntity<Language> getLanguage(@PathVariable("name") String name) {
20 return ResponseEntity.ok(languageService.getLanguage(name).orElseThrow(() -> new SpringMockMvcException(
21 HttpStatus.NOT_FOUND, "Language was not found")));
22 }
23
24 @ExceptionHandler(SpringMockMvcException.class)
25 public ResponseEntity<String> onSpringMockMvcException(HttpServletRequest request, SpringMockMvcException ex) {
26 return ResponseEntity.status(ex.getHttpStatus()).body(String.format("%s - %s",
27 ex.getHttpStatus().value(), ex.getMessage()));
28 }
29
30}
El controlador tiene una anotación global @RequestMapping
para especificar el mime-type para la salida de los endpoints declarados en la clase como application/json
. La clase expone dos endpoints que se apoyan en LanguageService
para realizar las operaciones de negocio.
El primero de los endpoints (/api/languages
) una lista de todos los lenguajes y opcionalmente podrá ser filtrada con un parámetro (contains
).