Spring Boot 3: doc, test y prep. para impl.: Aula 4

This commit is contained in:
devfzn 2023-09-19 17:37:13 -03:00
parent 7ebefa4fd4
commit f33e0b19ec
Signed by: devfzn
GPG Key ID: E070ECF4A754FDB1
10 changed files with 577 additions and 108 deletions

View File

@ -83,7 +83,13 @@
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.2.0</version>
</dependency>
<!--
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
-->
</dependencies>
<build>
<plugins>

View File

@ -1,5 +1,6 @@
package med.voll.api.controller;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import med.voll.api.domain.usuario.DatosAutenticacionUsuario;
import med.voll.api.domain.usuario.Usuario;
@ -17,6 +18,7 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/login")
@Tag(name = "Autenticacion", description = "Provee token para usuario, que da acceso a otros endpoints")
public class AutenticacionController {
@Autowired

View File

@ -1,5 +1,6 @@
package med.voll.api.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.validation.Valid;
import med.voll.api.domain.consulta.AgendaDeConsultaService;
@ -26,6 +27,7 @@ public class ConsultaController {
@PostMapping
@Transactional // ojo de donde se importa esta anotación
@Operation(summary = "Registra una consulta en la base de datos")
public ResponseEntity agendar(@RequestBody @Valid DatosAgendarConsulta datos) {
System.out.println(datos);
var response = service.agendar(datos);
@ -34,6 +36,7 @@ public class ConsultaController {
}
@GetMapping
@Operation(summary = "Retorna listado de consultas")
public ResponseEntity<Page<DatosDetalleConsulta>> listar(
@PageableDefault(size = 10, sort = {"fecha"}) Pageable paginacion) {
var response = service.consultar(paginacion);
@ -42,6 +45,7 @@ public class ConsultaController {
@DeleteMapping
@Transactional
@Operation(summary = "Cancela una consulta")
public ResponseEntity cancelar(@RequestBody @Valid DatosCancelarConsulta dados) {
service.cancelar(dados);
return ResponseEntity.noContent().build();

View File

@ -1,5 +1,6 @@
package med.voll.api.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
@ -24,6 +25,7 @@ public class MedicoController {
private MedicoRepository medicoRepository;
@PostMapping
@Operation(summary = "Registra un nuevo medico en la base de datos")
public ResponseEntity<DatosRespuestaMedico> registrarMedico(
@RequestBody @Valid DatosRegistroMedico datosRegistroMedico,
UriComponentsBuilder uriComponentsBuilder) {
@ -36,6 +38,7 @@ public class MedicoController {
}
@GetMapping
@Operation(summary = "Retorna listado de medicos")
public ResponseEntity<Page<DatosListadoMedicos>> listadoMedicos(
@PageableDefault(size = 5) Pageable paginacion) {
return ResponseEntity.ok(medicoRepository.findByActivoTrue(paginacion).map(DatosListadoMedicos::new));
@ -43,6 +46,7 @@ public class MedicoController {
@PutMapping
@Transactional
@Operation(summary = "Actualiza los datos de un medico existente")
public ResponseEntity<DatosRespuestaMedico> actualizarMedico(
@RequestBody @Valid DatosActualizarMedico datosActualizarMedico) {
Medico medico = medicoRepository.getReferenceById(datosActualizarMedico.id());
@ -55,6 +59,7 @@ public class MedicoController {
@DeleteMapping("/{id}")
@Transactional
@Operation(summary = "Cambia el estado de un medico a inactivo")
public ResponseEntity eliminarMedico(@PathVariable Long id) {
Medico medico = medicoRepository.getReferenceById(id);
medico.desactivarMedico();
@ -62,6 +67,7 @@ public class MedicoController {
}
@GetMapping("/{id}")
@Operation(summary = "Retorna los registros del medico según Id")
public ResponseEntity<DatosRespuestaMedico> retornaDatosMedico(@PathVariable Long id) {
Medico medico = medicoRepository.getReferenceById(id);
DatosRespuestaMedico datosRespuestaMedico = new DatosRespuestaMedico(

View File

@ -1,5 +1,6 @@
package med.voll.api.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
@ -26,6 +27,7 @@ public class PacienteController {
private PacienteRepository pacienteRepository;
@PostMapping
@Operation(summary = "Registra un nuevo paciente")
public ResponseEntity<DatosRespuestaPaciente> registrarPaciente(
@RequestBody @Valid DatosRegistroPaciente datosRegistroPaciente,
UriComponentsBuilder uriComponentsBuilder) {
@ -38,6 +40,7 @@ public class PacienteController {
}
@GetMapping
@Operation(summary = "Retorna listado de pacientes")
public ResponseEntity<Page<DatosListadoPacientes>> listadoPacientes(
@PageableDefault(size = 5) Pageable paginacion) {
return ResponseEntity.ok(
@ -47,6 +50,7 @@ public class PacienteController {
@PutMapping
@Transactional
@Operation(summary = "Actualiza información del paciente")
public ResponseEntity<DatosRespuestaPaciente> actualizarPaciente(
@RequestBody @Valid DatosActualizarPaciente datosActualizarPaciente) {
Paciente paciente = pacienteRepository.getReferenceById(datosActualizarPaciente.id());
@ -60,6 +64,7 @@ public class PacienteController {
// Desactivar Paciente
@DeleteMapping("/{id}")
@Transactional
@Operation(summary = "Cambia el estado de un paciente a inactivo")
public ResponseEntity eliminarPaciente(@PathVariable Long id) {
Paciente paciente = pacienteRepository.getReferenceById(id);
paciente.desactivarPaciente();
@ -67,6 +72,7 @@ public class PacienteController {
}
@GetMapping("/{id}")
@Operation(summary = "Retorna los detalles del paciente según ID")
public ResponseEntity<DatosRespuestaPaciente> retornaDatosPaciente(@PathVariable Long id) {
Paciente paciente = pacienteRepository.getReferenceById(id);
DatosRespuestaPaciente datosRespuestaPaciente = new DatosRespuestaPaciente(

View File

@ -0,0 +1,5 @@
spring:
datasource:
url: jdbc:mysql://${TEST_DB_URL}?createDatabaseIfNotExist=true&serverTimezone=UTC
username: ${DB_USER}
password: ${DB_PASS}

View File

@ -1,13 +0,0 @@
package med.voll.api;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ApiApplicationTests {
@Test
void contextLoads() {
}
}

View File

@ -0,0 +1,88 @@
package med.voll.api.controller;
import med.voll.api.domain.consulta.AgendaDeConsultaService;
import med.voll.api.domain.consulta.DatosAgendarConsulta;
import med.voll.api.domain.consulta.DatosDetalleConsulta;
import med.voll.api.domain.medico.Especialidad;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.json.JacksonTester;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureJsonTesters
@ActiveProfiles("test")
class ConsultaControllerTest {
@Autowired
private MockMvc mvc;
@Autowired
private JacksonTester<DatosAgendarConsulta> agendarConsultaJacksonTester;
@Autowired
private JacksonTester<DatosDetalleConsulta> detalleConsultaJacksonTester;
@MockBean
private AgendaDeConsultaService agendaDeConsultaService;
@Test
@DisplayName("Debe retornar estado http 400 cuando datos ingresados no sean válidos")
@WithMockUser
void agendarEscenario1() throws Exception {
// given - when
var response = mvc.perform(post("/consultas")).andReturn().getResponse();
// then
assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatus());
}
@Test
@DisplayName("Debe retornar estado http 200 cuando datos ingresados sean válidos")
@WithMockUser
void agendarEscenario2() throws Exception {
//given
var fecha = LocalDateTime.now().plusHours(1);
var especialidad = Especialidad.CARDIOLOGIA;
var datos = new DatosDetalleConsulta(null,2l,5l,fecha);
// when
when(agendaDeConsultaService.agendar(any())).thenReturn(datos);
var response = mvc.perform(post("/consultas")
.contentType(MediaType.APPLICATION_JSON)
.content(agendarConsultaJacksonTester.write(
new DatosAgendarConsulta(
2l,
5l,
fecha,
especialidad))
.getJson()))
.andReturn().getResponse();
//then
assertEquals(HttpStatus.OK.value(), response.getStatus());
var jsonEsperado = detalleConsultaJacksonTester.write(datos).getJson();
assertEquals(response.getContentAsString(), jsonEsperado);
}
}

View File

@ -0,0 +1,115 @@
package med.voll.api.domain.medico;
import med.voll.api.domain.consulta.Consulta;
import med.voll.api.domain.direccion.DatosDireccion;
import med.voll.api.domain.paciente.DatosRegistroPaciente;
import med.voll.api.domain.paciente.Paciente;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.ActiveProfiles;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAdjusters;
import static org.junit.jupiter.api.Assertions.*;
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ActiveProfiles("test")
class MedicoRepositoryTest {
@Autowired
private MedicoRepository medicoRepository;
@Autowired
private TestEntityManager em;
@Test
@DisplayName("Debe retornar null cuando el médico tenga una consulta en ese horario")
void seleccionarMedicoConEspecialidadEnFechaEscenario1() {
var proximoLunes10H = LocalDate.now()
.with(TemporalAdjusters.next(DayOfWeek.MONDAY))
.atTime(10, 0);
var medico = registrarMedico("Jose","jose@mail.com", "123456", Especialidad.CARDIOLOGIA);
var paciente = registrarPaciente("antonio","antonio@mail.com","654321");
registrarConsulta(medico, paciente, proximoLunes10H);
var medicoLibre = medicoRepository.seleccionarMedicoConEspecialidadEnFecha(
Especialidad.CARDIOLOGIA,
proximoLunes10H) ;
assertNull(medicoLibre);
}
@Test
@DisplayName("Debe retornar el médico ingresado como disponible en ese horario")
void seleccionarMedicoConEspecialidadEnFechaEscenario2() {
var proximoLunes10H = LocalDate.now()
.with(TemporalAdjusters.next(DayOfWeek.MONDAY))
.atTime(10, 0);
var medico = registrarMedico("Jose","jose@mail.com", "123456", Especialidad.CARDIOLOGIA);
var medicoLibre = medicoRepository.seleccionarMedicoConEspecialidadEnFecha(
Especialidad.CARDIOLOGIA,
proximoLunes10H) ;
assertEquals(medicoLibre, medico);
}
private void registrarConsulta(Medico medico, Paciente paciente, LocalDateTime fecha) {
em.persist(new Consulta(null, medico, paciente, fecha, null));
}
private Medico registrarMedico(String nombre, String email, String documento, Especialidad especialidad) {
var medico = new Medico(datosMedico(nombre, email, documento, especialidad));
em.persist(medico);
return medico;
}
private Paciente registrarPaciente(String nombre, String email, String documento) {
var paciente = new Paciente(datosPaciente(nombre, email, documento));
em.persist(paciente);
return paciente;
}
private DatosRegistroMedico datosMedico(String nombre, String email, String documento, Especialidad especialidad) {
return new DatosRegistroMedico(
nombre,
email,
"61999999999",
documento,
especialidad,
datosDireccion()
);
}
private DatosRegistroPaciente datosPaciente(String nombre, String email, String documento) {
return new DatosRegistroPaciente(
nombre,
email,
"61999999999",
documento,
datosDireccion()
);
}
private DatosDireccion datosDireccion() {
return new DatosDireccion(
"loca",
"azul",
"Acapulco",
"321",
"12"
);
}
}

View File

@ -969,3 +969,253 @@ por token.
---
## Pruebas Automatizadas
***¿Que se prueba?***
- Cotroller -> API
- Services -> Reglas de Negocio
- Repository -> Queries
#### Tipos de Tests
- Test de caja negra
- Test de caja blanca
### Propiedades de los Tests
[application-test.yml](./api_rest/api3/src/main/resources/application-test.yml)
```yml
spring:
datasource:
url: jdbc:mysql://${TEST_DB_URL}?createDatabaseIfNotExist=true&serverTimezone=UTC
username: ${DB_USER}
password: ${DB_PASS}
```
### Creación de Tests
#### Test MedicoRepository
Se crea el *Package*
[`test.java.med.voll.api.domain.medico`](./api_rest/api3/src/test/java/med/voll/api/domain/medico/)
y la clase
[MedicoRepositoryTest](./api_rest/api3/src/test/java/med/voll/api/domain/medico/MedicoRepositoryTest.java)
```java
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ActiveProfiles("test")
class MedicoRepositoryTest {
@Autowired
private MedicoRepository medicoRepository;
@Autowired
private TestEntityManager em;
@Test
@DisplayName("Debe retornar null cuando el médico tenga una consulta en ese horario")
void seleccionarMedicoConEspecialidadEnFechaEscenario1() {
var proximoLunes10H = LocalDate.now()
.with(TemporalAdjusters.next(DayOfWeek.MONDAY))
.atTime(10, 0);
var medico = registrarMedico("Jose",
"jose@mail.com",
"123456",
Especialidad.CARDIOLOGIA);
var paciente = registrarPaciente("antonio","antonio@mail.com","654321");
registrarConsulta(medico, paciente, proximoLunes10H);
var medicoLibre = medicoRepository.seleccionarMedicoConEspecialidadEnFecha(
Especialidad.CARDIOLOGIA,
proximoLunes10H);
assertNull(medicoLibre);
}
@Test
@DisplayName("Debe retornar el médico ingresado como disponible en ese horario")
void seleccionarMedicoConEspecialidadEnFechaEscenario2() {
var proximoLunes10H = LocalDate.now()
.with(TemporalAdjusters.next(DayOfWeek.MONDAY))
.atTime(10, 0);
var medico = registrarMedico("Jose",
"jose@mail.com",
"123456",
Especialidad.CARDIOLOGIA);
var medicoLibre = medicoRepository.seleccionarMedicoConEspecialidadEnFecha(
Especialidad.CARDIOLOGIA,
proximoLunes10H) ;
assertEquals(medicoLibre, medico);
}
private void registrarConsulta(Medico medico,
Paciente paciente,
LocalDateTime fecha) {
em.persist(new Consulta(null, medico, paciente, fecha, null));
}
private Medico registrarMedico(String nombre,
String email,
String documento,
Especialidad especialidad) {
var medico = new Medico(datosMedico(nombre, email, documento, especialidad));
em.persist(medico);
return medico;
}
private Paciente registrarPaciente(String nombre,
String email,
String documento) {
var paciente = new Paciente(datosPaciente(nombre, email, documento));
em.persist(paciente);
return paciente;
}
private DatosRegistroMedico datosMedico(String nombre,
String email,
String documento,
Especialidad especialidad) {
return new DatosRegistroMedico(
nombre,
email,
"61999999999",
documento,
especialidad,
datosDireccion()
);
}
private DatosRegistroPaciente datosPaciente(String nombre,
String email,
String documento) {
return new DatosRegistroPaciente(
nombre,
email,
"61999999999",
documento,
datosDireccion()
);
}
private DatosDireccion datosDireccion() {
return new DatosDireccion(
"Loca",
"Azul",
"Acapulco",
"321",
"12"
);
}
}
```
### Test ConsultaController
Clase
[ConsultaControllerTest](./api_rest/api3/src/test/java/med/voll/api/controller/ConsultaControllerTest.java)
en *package*
[`test.java.med.voll.api.controler`](./api_rest/api3/src/test/java/med/voll/api/controller/)
```java
package med.voll.api.controller;
import med.voll.api.domain.consulta.AgendaDeConsultaService;
import med.voll.api.domain.consulta.DatosAgendarConsulta;
import med.voll.api.domain.consulta.DatosDetalleConsulta;
import med.voll.api.domain.medico.Especialidad;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.json.JacksonTester;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureJsonTesters
@ActiveProfiles("test")
class ConsultaControllerTest {
@Autowired
private MockMvc mvc;
@Autowired
private JacksonTester<DatosAgendarConsulta> agendarConsultaJacksonTester;
@Autowired
private JacksonTester<DatosDetalleConsulta> detalleConsultaJacksonTester;
@MockBean
private AgendaDeConsultaService agendaDeConsultaService;
@Test
@DisplayName("Debe retornar estado http 400 cuando datos ingresados no sean válidos")
@WithMockUser
void agendarEscenario1() throws Exception {
// given - when
var response = mvc.perform(post("/consultas")).andReturn().getResponse();
// then
assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatus());
}
@Test
@DisplayName("Debe retornar estado http 200 cuando datos ingresados sean válidos")
@WithMockUser
void agendarEscenario2() throws Exception {
//given
var fecha = LocalDateTime.now().plusHours(1);
var especialidad = Especialidad.CARDIOLOGIA;
var datos = new DatosDetalleConsulta(null,2l,5l,fecha);
// when
when(agendaDeConsultaService.agendar(any())).thenReturn(datos);
var response = mvc.perform(post("/consultas")
.contentType(MediaType.APPLICATION_JSON)
.content(agendarConsultaJacksonTester.write(
new DatosAgendarConsulta(
2l,
5l,
fecha,
especialidad))
.getJson()))
.andReturn().getResponse();
//then
assertEquals(HttpStatus.OK.value(), response.getStatus());
var jsonEsperado = detalleConsultaJacksonTester.write(datos).getJson();
assertEquals(response.getContentAsString(), jsonEsperado);
}
}
```
---
## Build del proyecto