CodeGym /Cursos /Módulo 5. Spring /Lección 137: Práctica: escribir tests de integración para...

Lección 137: Práctica: escribir tests de integración para REST API

Módulo 5. Spring
Nivel 9 , Lección 6
Disponible

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:

  1. Base de datos de prueba. Para aislar usamos una base de datos embebida (por ejemplo, H2).
  2. 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.
  3. Anotaciones apropiadas. Usamos la anotación @SpringBootTest para 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:

  1. Usamos la anotación @SpringBootTest para arrancar el contexto de Spring.
  2. Usamos MockMvc para 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
    }
}
  1. La anotación @SpringBootTest carga todo el contexto de la aplicación, incluyendo la base de datos.
  2. La anotación @AutoConfigureMockMvc configura MockMvc para simular peticiones HTTP.
  3. En el test shouldCreateUser enviamos una petición POST para crear un nuevo usuario y comprobamos que el servidor responde con código 201 y devuelve los datos correctos.
  4. En el test shouldRetrieveUserById verificamos que un usuario existente puede ser encontrado por su ID.
  5. En el test shouldReturnNotFoundWhenUserDoesNotExist comprobamos 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 cleanDatabase del 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.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION