Ao rodar o teste de cenário 1 do ConsultaControllerTest aparece o seguinte:
Não encontro solução.
Ao rodar o teste de cenário 1 do ConsultaControllerTest aparece o seguinte:
Não encontro solução.
Oi!
Esse erro acontece mesmo e foi explicado no vídeo, lá pelo minuto 10:20: https://cursos.alura.com.br/course/spring-boot-3-documente-teste-prepare-api-deploy/task/122056
A adição da anotação @WithMockUser acima dos cenários funcionou para o 2(que também estava falhando), mas o cenário 1 continua falhando no erro:
MockHttpServletRequest: HTTP Method = POST Request URI = /consultas Parameters = {} Headers = [] Body = null Session Attrs = {}
Handler: Type = med.voll.api.controller.ConsultaController Method = med.voll.api.controller.ConsultaController#agendar(DadosAgendamentoConsulta)
Async: Async started = false Async result = null
Resolved Exception: Type = org.springframework.http.converter.HttpMessageNotReadableException
ModelAndView: View name = null View = null Model = null
FlashMap: Attributes = null
MockHttpServletResponse: Status = 500 Error message = null Headers = [Content-Type:"text/plain;charset=UTF-8", Content-Length:"187", X-Content-Type-Options:"nosniff", X-XSS-Protection:"0", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"] Content type = text/plain;charset=UTF-8 Body = Erro: Required request body is missing: public org.springframework.http.ResponseEntity med.voll.api.controller.ConsultaController.agendar(med.voll.api.domain.dto.DadosAgendamentoConsulta) Forwarded URL = null Redirected URL = null Cookies = []
org.opentest4j.AssertionFailedError: expected: 400 but was: 500 Expected :400 Actual :500
Agora mudou para erro 500. Manda aqui o código da sua classe controller
Mando as duas classes controller (normal e teste)
Classe ControllerConsulta
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import med.voll.api.domain.Consulta.AgendaDeConsultas;
import med.voll.api.domain.dto.DadosAgendamentoConsulta;
import med.voll.api.domain.dto.DadosCancelamentoConsulta;
import med.voll.api.domain.dto.DadosDetalhamentoConsulta;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("consultas")
@SecurityRequirement(name = "bearer-key")
public class ConsultaController {
@Autowired
private AgendaDeConsultas agenda;
@PostMapping
@Transactional
public ResponseEntity agendar(@RequestBody @Valid DadosAgendamentoConsulta dados){
var dto = agenda.agendar(dados);
return ResponseEntity.ok(dto);
}
Classe ControllerConsultaTest
import med.voll.api.domain.Consulta.AgendaDeConsultas;
import med.voll.api.domain.dto.DadosAgendamentoConsulta;
import med.voll.api.domain.dto.DadosDetalhamentoConsulta;
import med.voll.api.domain.medico.Especialidade;
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.web.servlet.MockMvc;
import java.time.LocalDateTime;
import static org.assertj.core.api.Assertions.assertThat;
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
class ConsultaControllerTest {
@Autowired
private MockMvc mvc;
@Autowired
private JacksonTester<DadosAgendamentoConsulta> dadosAgendamentoConsultaJson;
@Autowired
private JacksonTester<DadosDetalhamentoConsulta> dadosDetalhamentoConsultaJson;
@MockBean
private AgendaDeConsultas agendaDeConsultas;
@Test
@DisplayName("Deveria devolver codigo http 400 quando informacoes estao invalidas")
@WithMockUser
void agendar_cenario1() throws Exception {
var response = mvc.perform(post("/consultas"))
.andReturn().getResponse();
assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@Test
@DisplayName("Deveria devolver codigo http 200 quando informacoes estao validas")
@WithMockUser
void agendar_cenario2() throws Exception {
var data = LocalDateTime.now().plusHours(1);
var especialidade = Especialidade.CARDIOLOGIA;
var dadosDetalhamento = new DadosDetalhamentoConsulta(null, 2l, 2l, data);
when(agendaDeConsultas.agendar(any())).thenReturn(dadosDetalhamento);
var response = mvc
.perform(
post("/consultas")
.contentType(MediaType.APPLICATION_JSON)
.content(dadosAgendamentoConsultaJson.write(
new DadosAgendamentoConsulta(2L, 2L, data, especialidade)
).getJson())
)
.andReturn().getResponse();
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
var jsonEsperado = dadosDetalhamentoConsultaJson.write(
dadosDetalhamento
).getJson();
assertThat(response.getContentAsString()).isEqualTo(jsonEsperado);
}
}
Os códigos estão corretos.
Manda aqui as suas classes: SecurityConfigurations e SecurityFilter, pois pode ser algum problema na configuração de segurança.
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfigurations {
@Autowired
private SecurityFilter securityFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.csrf(csrf -> csrf.disable())
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(req -> {
req.requestMatchers("/login").permitAll();
req.requestMatchers("/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**").permitAll();
req.anyRequest().authenticated();
})
.addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception{
return configuration.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public OpenAPI configDoc(){
return new OpenAPI()
.info(new Info()
.title("Forum Oracle One ")
.version("1.0.0")
.description("Aplicação do Curso Oracle ONE"))
.components(new Components()
.addSecuritySchemes("bearer-key",
new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")));
}
}
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import med.voll.api.domain.usuario.UsuarioRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class SecurityFilter extends OncePerRequestFilter {
@Autowired
private TokenService tokenService;
@Autowired
private UsuarioRepository repository;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
var tokenJWT = recuperarToken(request);
if (tokenJWT != null) {
var subject = tokenService.getSubject(tokenJWT);
var usuario = repository.findByLogin(subject);
var authentication = new UsernamePasswordAuthenticationToken(usuario, null, usuario.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
System.out.println("logado na requisição");
}
filterChain.doFilter(request, response);
}
private String recuperarToken(HttpServletRequest request) {
var authorizationHeader = request.getHeader("Authorization");
if (authorizationHeader != null) {
return authorizationHeader.replace("Bearer ", "").trim();
}
return null;
}
}
Está tudo certo também :D
Não consegui identificar o problema. você consegue compartilhar o projeto?
Está faltando esse tratamento de erro, na sua classe TratadorDeErros:
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity tratarErro400(HttpMessageNotReadableException ex) {
return ResponseEntity.badRequest().body(ex.getMessage());
}
Ele foi incluído no curso anterior, nessa atividade: https://cursos.alura.com.br/course/spring-boot-aplique-boas-praticas-proteja-api-rest/task/125341
Depois que adicionei esse tratamento de erros os dois testes do ConsultaControllerTest funcionaram como esperado. Porém, na implementação do MedicoControllerTest só o teste do primeiro cenário funcionou, enquanto que o teste do cenário 2 falhou e apresentou o seguinte erro:
:: Spring Boot :: (v3.3.4)
(...)
2024-12-12T10:59:11.411-03:00 WARN 14588 --- [api] [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2024-12-12T10:59:12.509-03:00 INFO 14588 --- [api] [ main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2024-12-12T10:59:12.509-03:00 INFO 14588 --- [api] [ main] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet ''
2024-12-12T10:59:12.525-03:00 INFO 14588 --- [api] [ main] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 16 ms
2024-12-12T10:59:12.572-03:00 INFO 14588 --- [api] [ main] m.v.api.controller.MedicoControllerTest : Started MedicoControllerTest in 9.3 seconds (process running for 11.288)
WARNING: A Java agent has been loaded dynamically (C:\Users\chiar\.m2\repository\net\bytebuddy\byte-buddy-agent\1.14.19\byte-buddy-agent-1.14.19.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release
Java HotSpot(TM) 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
org.springframework.dao.InvalidDataAccessApiUsageException: Entity must not be null
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:371)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:246)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:550)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:335)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:160)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudM
(...)
Método do teste do cenário 2:
@Test
@DisplayName("Deveria devolver codigo http 201 quando informacoes estao validas")
@WithMockUser
void cadastrar_cenario2() throws Exception {
var dadosCadastro = new DadosCadastroMedico(
"Medico",
"medico@voll.med",
"619999",
"123456",
Especialidade.CARDIOLOGIA,
dadosEndereco());
when(repository.save(any())).thenReturn(new Medico(dadosCadastro));
var response = mvc
.perform(post("/medicos")
.contentType(MediaType.APPLICATION_JSON)
.content(dadosCadastroMedicoJson.write(dadosCadastro).getJson()))
.andReturn().getResponse();
var dadosDetalhamento = new DadosDetalhamentoMedico(
null,
dadosCadastro.nome(),
dadosCadastro.email(),
dadosCadastro.crm(),
dadosCadastro.telefone(),
dadosCadastro.especialidade(),
new Endereco(dadosCadastro.endereco())
);
var jsonEsperado = dadosDetalhamentoMedicoJson.write(dadosDetalhamento).getJson();
assertThat(response.getStatus()).isEqualTo(HttpStatus.CREATED.value());
assertThat(response.getContentAsString()).isEqualTo(jsonEsperado);
}
private DadosEndereco dadosEndereco() {
return new DadosEndereco(
"rua xpto",
"bairro",
"00000000",
"Brasilia",
"DF",
null,
null
);
}
}
Confere se o atributo repository está anotado com @MockBean:
@MockBean
private MedicoRepository repository;
Estava anotado como @Autowired, ao mudar para @MockBean deu certo. Fiquei na dúvida se essa mudança é para definir o repository no ambiente Mockito, é isso?
Isso mesmo. Nesse teste o repository será um mock e não uma instância real do repository.
Beleza, obrigado pela ajuda e pelos esclarecimentos. Abraço,