Los tests de integración verifican la interacción entre varios componentes de la aplicación. Si los Unit-tests analizan cada componente aisladamente, aquí, al contrario, nos interesa si funcionan correctamente en conjunto. Por ejemplo, ¿podemos obtener datos desde la base a través del controlador?, ¿se procesan bien las peticiones y se comunican correctamente los servicios entre sí?
¿Por qué son importantes?
- Compruebas el interacción real de los componentes. Un REST API sin una capa de servicio que funcione — es solo decoración, convendrás.
- Confianza en la funcionalidad: los tests de integración ayudan a saber que tu aplicación hace lo que debe.
- Detección de errores de configuración: a menudo los fallos no están en el código, sino en la configuración de los componentes (por ejemplo, la conexión a la base de datos).
¿Cómo prepararse para los tests de integración?
Como vamos a interactuar con todas las capas de nuestra aplicación, necesitaremos configurar los siguientes elementos:
- Base de datos de prueba. Para aislar usamos una base de datos embebida (por ejemplo, H2).
- Arranque de todo el contexto de Spring. La idea de los tests de integración es probar la aplicación como si se ejecutara en un entorno real.
- Anotaciones apropiadas. Usamos la anotación
@SpringBootTestpara cargar todo el contexto de la aplicación.
Preparación del entorno
Crear la base de datos de prueba
En el archivo application.properties añadiremos la configuración para conectar con H2. Eso nos permitirá ejecutar los tests sin depender de la base de datos real:
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
Esta configuración creará la base de datos "al vuelo", que se eliminará tras finalizar los tests.
Escribiendo tests de integración
Veamos una aplicación que trabaja con la entidad User. Tenemos un REST API que permite realizar operaciones CRUD (create, read, update y delete) sobre usuarios.
Ejemplo de nuestra entidad User
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// Getters y setters...
}
Repositorio para trabajar con la base de datos
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
Controlador para manejar las peticiones
@RestController
@RequestMapping("/users")
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
User savedUser = userRepository.save(user);
return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
return userRepository.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
// Resto de métodos CRUD...
}
Clase de test
Para las pruebas de integración crearemos una clase de test. Vamos a comprobar la interacción entre el controlador y el repositorio.
Pasos:
- Usamos la anotación
@SpringBootTestpara arrancar el contexto de Spring. - Usamos
MockMvcpara simular peticiones HTTP y comprobar las respuestas.
@SpringBootTest
@AutoConfigureMockMvc
class UserIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private UserRepository userRepository;
@BeforeEach
void cleanDatabase() {
userRepository.deleteAll();
}
@Test
void shouldCreateUser() throws Exception {
// Creamos el objeto User para enviar
User user = new User();
user.setName("John Doe");
user.setEmail("john.doe@example.com");
// Ejecutamos la petición POST
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isCreated()) // Comprobamos que el estado sea 201 Created
.andExpect(jsonPath("$.id").exists()) // Comprobamos que la respuesta tiene ID
.andExpect(jsonPath("$.name").value("John Doe")) // Comprobamos el nombre
.andExpect(jsonPath("$.email").value("john.doe@example.com")); // Comprobamos el email
}
@Test
void shouldRetrieveUserById() throws Exception {
// Guardamos el usuario en la base de datos
User user = new User();
user.setName("Jane Doe");
user.setEmail("jane.doe@example.com");
user = userRepository.save(user);
// Ejecutamos la petición GET
mockMvc.perform(get("/users/{id}", user.getId())
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) // Comprobamos que el estado sea 200 OK
.andExpect(jsonPath("$.id").value(user.getId()))
.andExpect(jsonPath("$.name").value("Jane Doe"))
.andExpect(jsonPath("$.email").value("jane.doe@example.com"));
}
@Test
void shouldReturnNotFoundWhenUserDoesNotExist() throws Exception {
// Ejecutamos GET con un ID inexistente
mockMvc.perform(get("/users/{id}", 999)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound()); // Comprobamos que devuelve 404
}
}
- La anotación
@SpringBootTestcarga todo el contexto de la aplicación, incluyendo la base de datos. - La anotación
@AutoConfigureMockMvcconfiguraMockMvcpara simular peticiones HTTP. - En el test
shouldCreateUserenviamos una petición POST para crear un nuevo usuario y comprobamos que el servidor responde con código 201 y devuelve los datos correctos. - En el test
shouldRetrieveUserByIdverificamos que un usuario existente puede ser encontrado por su ID. - En el test
shouldReturnNotFoundWhenUserDoesNotExistcomprobamos que una petición a un recurso inexistente devuelve estado 404.
Errores comunes y trucos
Si has escrito tests alguna vez, probablemente sabes que "si algo puede salir mal, saldrá mal". Aquí van formas de minimizar problemas:
- Problema: la base de datos contiene "basura". Si tests anteriores dejan datos en la DB, eso puede afectar a los siguientes. Siempre limpia la base antes de cada test (como en el método
cleanDatabasedel ejemplo). - Error de configuración de la base de datos. Asegúrate de que la base de prueba se está usando realmente para los tests y no tu BD principal. ¡Revisa el
application.properties! - Problemas con MockMvc. Si te olvidas de la anotación
@AutoConfigureMockMvc, los tests que usan MockMvc no funcionarán. - Revisa los mensajes de error. Cuando algo falla, estudia el stack trace — muchas veces el problema está en un detalle pequeño.
Ahora tienes las herramientas necesarias para escribir tests de integración para REST API. Puedes aplicar esta práctica en proyectos reales para asegurarte de que tu aplicación cumple con lo esperado y se comporta como debe. El ejemplo anterior también es útil para preparar entrevistas, donde a menudo piden ejemplos de tests.
GO TO FULL VERSION