Hallazgos
El test no está probando lo que parece
SuperheroeServiceImplTest declara:
@Autowired
private SuperheroeService service;
@Mock
private SuperheroesRepository repository;
El @Mock nunca se inyecta en service (no hay @InjectMocks,
@MockBean ni MockitoAnnotations.openMocks(this)/MockitoExtension).
El service autowireado es el bean real, conectado al repositorio real contra la H2 en
memoria sembrada por Liquibase con los 10 héroes de superheroes.sql. Los
doReturn(...).when(repository)... no tienen ningún efecto: son un mock huérfano.
Esto explica por qué getSuperheroByID_isOK_thenReturnStatus200 "funciona" y espera
"Batman" para id = 2: no es porque el mock devuelva ese valor, es que
coincide por casualidad con el dato semilla real. Es un test de integración disfrazado de test
unitario, y frágil ante cualquier cambio en superheroes.sql.
Cómo arreglarlo: añadir @ExtendWith(MockitoExtension.class) (o
MockitoAnnotations.openMocks(this) en un @BeforeEach) e inyectar el mock con
@InjectMocks SuperheroeServiceImpl service en lugar de @Autowired +
@SpringBootTest, para que sea un test unitario real y rápido, sin levantar contexto de
Spring ni base de datos.
Inconsistencia Java 11 vs bytecode 1.8
pom.xml declara <java.version>11</java.version> pero el
maven-compiler-plugin está configurado con <source>1.8</source>
<target>1.8</target>. El enunciado pedía explícitamente Java 11.
Cambiar el plugin a <source>11</source><target>11</target> (o mejor,
usar <maven.compiler.release>11</maven.compiler.release> y eliminar la
propiedad duplicada).
Puntos opcionales del enunciado sin implementar
- Anotación personalizada de tiempos de ejecución. Crear
@LogExecutionTime+@Aspectcon Spring AOP (spring-boot-starter-aop) que envuelva el método anotado, mida conSystem.nanoTime()y escriba el resultado con un logger. Aplicarla sobre los métodos deSuperheroeServiceImpl. - Gestión centralizada de excepciones. Añadir un
@RestControllerAdvicecon@ExceptionHandlerparaMethodArgumentNotValidException,ConstraintViolationExceptiony una excepción de negocio propia (p. ej.SuperheroeNotFoundException), devolviendo siempre unGenericResponsecoherente en vez de las validaciones manuales repetidas en el controlador. - Test de integración. Añadir un
@SpringBootTestcon@AutoConfigureMockMvcque golpee los endpoints reales (MockMvcoWebTestClient) contra la H2 en memoria, complementando el test unitario del servicio. - Dockerización. Añadir un
Dockerfilemulti-stage (build conmvnw+ imagen finaleclipse-temurin:11-jre) y opcionalmente undocker-compose.yml. - Caché de peticiones. Añadir
spring-boot-starter-cache+@EnableCaching, y anotargetAllSuperheroes()/getSuperheroByID()con@Cacheable, invalidando la caché enupdateSuperhero()ydeleteSuperhero()con@CacheEvict. - Documentación de la API. Ya está la dependencia
spring-restdocs-mockmvcen elpom.xmlpero no se llegó a generar documentación con ella. Más simple y mantenible hoy: añadirspringdoc-openapi-uipara Swagger UI autogenerado, o completar la configuración de REST Docs ya presente. - Seguridad del API. No hay ninguna dependencia de seguridad. Como mínimo, añadir
spring-boot-starter-securitycon autenticación básica o un API key para los endpoints de escritura (PUT/DELETE), y sustituir@CrossOrigin(origins = "*")(abierto a cualquier origen) por una lista blanca de orígenes concreta.
Diseño de la API
- No es RESTful. Las rutas usan el verbo en la propia URL
(
GET /get/all,GET /get/one/{id},PUT /put,DELETE /delete/{id}) en vez de apoyarse en el método HTTP: debería serGET /superheroes,GET /superheroes/{id},GET /superheroes?name=man,PUT /superheroes/{id},DELETE /superheroes/{id}. - El
PUTno lleva el id en la URL (SuperheroesController.putExample): recibe el id dentro del body (SuperheroeDAO.id). Debería serPUT /superheroes/{id}con el id como@PathVariable, y el body solo con los campos a modificar. - Nombres de método heredados de la plantilla.
putExampleydeleteExampleenSuperheroesControllerson nombres de ejemplo de Spring Initializr que no se renombraron; deberían llamarseupdateSuperheroe/deleteSuperheroe. URLConstant.POSTestá declarado y no se usa (no hay endpoint de creación, ni lo pedía el enunciado). Si no se va a añadir unPOST, eliminar la constante muerta; si se añade, usarla.- Falta paginación en
getAllSuperheroes(). ConJpaRepositoryya disponible, cambiar aPage<SuperheroeEntity> findAll(Pageable pageable)es casi gratis.
Validación
javax.validation está en el pom.xml y @Valid se usa en el
controlador, pero SuperheroeDAO no tiene ninguna anotación de validación
(@NotBlank, @Size, etc.), así que @Valid no hace nada ahí.
Los controles actuales son manuales y repetidos:
if(data == null || StringUtils.isAllEmpty(data.getName()) || StringUtils.isAllEmpty(data.getPower())) { ... }
Mover estas reglas a anotaciones en SuperheroeDAO (@NotBlank en
name/power, @Size(max = 25) / @Size(max = 50)
acorde a las columnas de superheroes.sql) y dejar que el
@RestControllerAdvice del punto 3 traduzca los errores de validación a
GenericResponse.
Logging
No hay ningún logger en el proyecto. Añadir SLF4J (LoggerFactory.getLogger(...)) al
menos en el servicio y en el aspecto de tiempos de ejecución del punto 3; ahora mismo no queda
ningún rastro de lo que hace la aplicación en producción.
Acoplamiento a tipos concretos
SuperheroesRepositorydeclaraArrayList<SuperheroeEntity> findAll()yArrayList<SuperheroeEntity> findSuperheroeEntitiesByNameContainingIgnoreCase(String param). Un repositorio Spring Data debería exponerList<T>, noArrayList<T>: acoplarse a la implementación concreta no aporta nada y sobreescribirfindAll()solo para cambiar el tipo de retorno es innecesario.- Mismo patrón en
SuperheroeMapper.arrayListSuperheroeEntityToArrayListSuperheroDTO: usarList<SuperheroeDTO>en la firma.
Mezcla de JAX-RS y Spring
El proyecto usa Spring Boot de principio a fin, pero para los códigos de estado tira de
javax.ws.rs.core.Response.Status (JAX-RS) en vez de
org.springframework.http.HttpStatus, y mete la dependencia
javax.ws.rs-api solo para eso. Sustituir por HttpStatus de Spring y
devolver ResponseEntity<GenericResponse> en los controladores en vez de fijar el
status solo dentro del GenericResponse (así el código HTTP real de la respuesta
coincide con el que se informa en el body).
Configuración
- La contraseña de la datasource (
spring.datasource.password=password) está hardcodeada enapplication.properties. Para una H2 en memoria de prueba el riesgo es mínimo, pero es un mal hábito a evitar incluso en pruebas técnicas. - No hay perfiles (
application-dev.properties,application-prod.properties) ni forma de parametrizar el puerto/entorno sin editar el fichero fuente.
Documentación y naming
- Mezcla de español ("Superheroe", "Prueba técnica") e inglés (nombres de clases, mensajes de respuesta) — no bloqueante, pero conviene decidir un idioma para el código y mantenerlo consistente.
- No hay Javadoc en las interfaces públicas (
SuperheroeService,SuperheroesRepository).
Resumen priorizado
- Arreglar el test que no testea nada real (punto 1) — es lo más grave, da falsa confianza.
- Alinear Java 11 en el compilador (punto 2) — incumple un requisito explícito del enunciado.
- Gestión centralizada de excepciones + validación con anotaciones (puntos 3 y 5) — mejora visible con poco esfuerzo.
- Rediseñar rutas a estilo REST (punto 4) — impacto en el contrato de la API, hacerlo antes de sumar más clientes.
- Resto de opcionales (AOP de tiempos, tests de integración, Docker, caché, seguridad, documentación OpenAPI) según tiempo disponible.