Solucionado (ver solução)
Solucionado
(ver solução)
10
respostas

Teste retorna 500 e não 400

Ao rodar a classe de teste abaixo, a asserção falha.

@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureJsonTesters
class MedicoControllerTest {

    @Autowired
    private MockMvc mvc;

    @Autowired
    private JacksonTester<DadosCadastroMedico> dadosCadastroMedicoJson;

    @Autowired
    private JacksonTester<DadosDetalhamentoMedico> dadosDetalhamentoMedicoJson;

    @MockBean
    private MedicoRepository repository;

    @Test
    @DisplayName("Deveria devolver codigo http 400 quando informacoes estao invalidas")
    @WithMockUser
    void cadastraCenario1() throws Exception {
        MockHttpServletResponse response = mvc
                .perform(post("/medicos")).andReturn()
                .getResponse();

        assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
    }
}

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

10 respostas

Oi!

Confere no MedicoController se a url correta é /medicos mesmo.

Então, realmente havia um erro na url, pois o certo é /medicos/cadastro. No entanto, continuou falhando. Agora retornou 500 e não 400.

@RestController
@RequestMapping("medicos")
@SecurityRequirement(name = "bearer-key")
public class MedicoController {

    @Autowired
    private MedicoRepository repository;

    @PostMapping("/cadastro")
    @Transactional
    public ResponseEntity<DadosDetalhamentoMedico> cadastra(@RequestBody @Valid DadosCadastroMedico dados,
            UriComponentsBuilder uriBuilder) {
        Medico medico = new Medico(dados);
        repository.save(medico);

        URI uri = uriBuilder.path("/medicos/cadastro/{id}").buildAndExpand(medico.getId()).toUri();

        return ResponseEntity.created(uri).body(new DadosDetalhamentoMedico(medico));
    }
}
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureJsonTesters
class MedicoControllerTest {

    @Autowired
    private MockMvc mvc;

    @Autowired
    private JacksonTester<DadosCadastroMedico> dadosCadastroMedicoJson;

    @Autowired
    private JacksonTester<DadosDetalhamentoMedico> dadosDetalhamentoMedicoJson;

    @MockBean
    private MedicoRepository repository;

    @Test
    @DisplayName("Deveria devolver codigo http 400 quando informacoes estao invalidas")
    @WithMockUser
    void cadastraCenario1() throws Exception {
        MockHttpServletResponse response = mvc
                .perform(post("/medicos/cadastro")).andReturn()
                .getResponse();

        assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
    }
}

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Codigo está certinho. Manda aqui a stack trace completa do erro

11:31:15.956 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [med.voll.api.controller.MedicoControllerTest]: MedicoControllerTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
11:31:16.075 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration med.voll.api.SpringbootApiApplication for test class med.voll.api.controller.MedicoControllerTest

2023-09-18T11:31:16.449-03:00  INFO 22756 --- [           main] m.v.api.controller.MedicoControllerTest  : Starting MedicoControllerTest using Java 17.0.5 with PID 22756 (started by jpmat in C:\Users\jpmat\Documents\java\formacao_java_alura_2\springboot_api)
2023-09-18T11:31:16.451-03:00  INFO 22756 --- [           main] m.v.api.controller.MedicoControllerTest  : No active profile set, falling back to 1 default profile: "default"
2023-09-18T11:31:17.176-03:00  INFO 22756 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2023-09-18T11:31:17.239-03:00  INFO 22756 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 55 ms. Found 4 JPA repository interfaces.
2023-09-18T11:31:18.657-03:00  INFO 22756 --- [           main] o.f.c.internal.license.VersionPrinter    : Flyway Community Edition 9.16.3 by Redgate
2023-09-18T11:31:18.657-03:00  INFO 22756 --- [           main] o.f.c.internal.license.VersionPrinter    : See release notes here: https://rd.gt/416ObMi
2023-09-18T11:31:18.657-03:00  INFO 22756 --- [           main] o.f.c.internal.license.VersionPrinter    : 
2023-09-18T11:31:18.674-03:00  INFO 22756 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2023-09-18T11:31:19.078-03:00  INFO 22756 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@335cdd2
2023-09-18T11:31:19.081-03:00  INFO 22756 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2023-09-18T11:31:19.116-03:00  INFO 22756 --- [           main] o.f.c.i.database.base.BaseDatabaseType   : Database: jdbc:mysql://localhost/vollmed_api (MySQL 8.0)
2023-09-18T11:31:19.178-03:00  INFO 22756 --- [           main] o.f.core.internal.command.DbValidate     : Successfully validated 8 migrations (execution time 00:00.034s)
2023-09-18T11:31:19.192-03:00  INFO 22756 --- [           main] o.f.core.internal.command.DbMigrate      : Current version of schema `vollmed_api`: 8
2023-09-18T11:31:19.193-03:00  INFO 22756 --- [           main] o.f.core.internal.command.DbMigrate      : Schema `vollmed_api` is up to date. No migration necessary.
2023-09-18T11:31:19.318-03:00  INFO 22756 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2023-09-18T11:31:19.392-03:00  INFO 22756 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 6.2.7.Final
2023-09-18T11:31:19.394-03:00  INFO 22756 --- [           main] org.hibernate.cfg.Environment            : HHH000406: Using bytecode reflection optimizer
2023-09-18T11:31:19.562-03:00  INFO 22756 --- [           main] o.h.b.i.BytecodeProviderInitiator        : HHH000021: Bytecode provider name : bytebuddy
2023-09-18T11:31:19.572-03:00  INFO 22756 --- [           main] o.s.o.j.p.SpringPersistenceUnitInfo      : No LoadTimeWeaver setup: ignoring JPA class transformer
2023-09-18T11:31:19.867-03:00  INFO 22756 --- [           main] o.h.b.i.BytecodeProviderInitiator        : HHH000021: Bytecode provider name : bytebuddy
2023-09-18T11:31:20.393-03:00  INFO 22756 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2023-09-18T11:31:20.395-03:00  INFO 22756 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2023-09-18T11:31:20.781-03:00  INFO 22756 --- [           main] o.s.d.j.r.query.QueryEnhancerFactory     : Hibernate is in classpath; If applicable, HQL parser will be used.
2023-09-18T11:31:21.327-03:00  WARN 22756 --- [           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
2023-09-18T11:31:21.421-03:00  INFO 22756 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@7be3baf2, 
2023-09-18T11:31:21.976-03:00  INFO 22756 --- [           main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2023-09-18T11:31:21.977-03:00  INFO 22756 --- [           main] o.s.t.web.servlet.TestDispatcherServlet  : Initializing Servlet ''
2023-09-18T11:31:21.978-03:00  INFO 22756 --- [           main] o.s.t.web.servlet.TestDispatcherServlet  : Completed initialization in 1 ms
2023-09-18T11:31:22.014-03:00  INFO 22756 --- [           main] m.v.api.controller.MedicoControllerTest  : Started MedicoControllerTest in 5.794 seconds (process running for 6.657)

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /medicos/cadastro
       Parameters = {}
          Headers = []
             Body = null
    Session Attrs = {}

Handler:
             Type = med.voll.api.controller.MedicoController
           Method = med.voll.api.controller.MedicoController#cadastra(DadosCadastroMedico, UriComponentsBuilder)

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:"286", 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.domain.medico.DadosDetalhamentoMedico> med.voll.api.controller.MedicoController.cadastra(med.voll.api.domain.medico.DadosCadastroMedico,org.springframework.web.util.UriComponentsBuilder)
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /medicos/cadastro
       Parameters = {}
          Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"248"]
             Body = {"nome":"Medico","email":"medico@voll.med","telefone":"61999999999","crm":"123456","especialidade":"CARDIOLOGIA","endereco":{"logradouro":"rua xpto","bairro":"bairro","cep":"00000000","cidade":"Brasilia","uf":"DF","complemento":null,"numero":null}}
    Session Attrs = {}

Handler:
             Type = med.voll.api.controller.MedicoController
           Method = med.voll.api.controller.MedicoController#cadastra(DadosCadastroMedico, UriComponentsBuilder)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = org.springframework.web.bind.MethodArgumentNotValidException

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 400
    Error message = null
          Headers = [Content-Type:"application/json", 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 = application/json
             Body = [{"campo":"endereco.cep","mensagem":"must match \"\\d{5}-\\d{3}\""}]
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
2023-09-18T11:31:22.482-03:00  INFO 22756 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2023-09-18T11:31:22.485-03:00  INFO 22756 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2023-09-18T11:31:22.498-03:00  INFO 22756 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

Está exigindo um json no corpo da requisição. Talvez seja a versão do Spring Bot urtilizada no seu projeto. Altera então o código:

mvc
    .perform(post("/medicos/cadastro")
    .contentType(MediaType.APPLICATION_JSON)
    .content("{}"))
    .andReturn().getResponse();

Fiz a alteração e mesmo assim continuou falhando.

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /medicos/cadastro
       Parameters = {}
          Headers = [Content-Type:"application/json;charset=UTF-8"]
             Body = 
    Session Attrs = {}

Handler:
             Type = med.voll.api.controller.MedicoController
           Method = med.voll.api.controller.MedicoController#cadastra(DadosCadastroMedico, UriComponentsBuilder)

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:"286", 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.domain.medico.DadosDetalhamentoMedico> med.voll.api.controller.MedicoController.cadastra(med.voll.api.domain.medico.DadosCadastroMedico,org.springframework.web.util.UriComponentsBuilder)
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /medicos/cadastro
       Parameters = {}
          Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"248"]
             Body = {"nome":"Medico","email":"medico@voll.med","telefone":"61999999999","crm":"123456","especialidade":"CARDIOLOGIA","endereco":{"logradouro":"rua xpto","bairro":"bairro","cep":"00000000","cidade":"Brasilia","uf":"DF","complemento":null,"numero":null}}
    Session Attrs = {}

Handler:
             Type = med.voll.api.controller.MedicoController
           Method = med.voll.api.controller.MedicoController#cadastra(DadosCadastroMedico, UriComponentsBuilder)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = org.springframework.web.bind.MethodArgumentNotValidException

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 400
    Error message = null
          Headers = [Content-Type:"application/json", 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 = application/json
             Body = [{"campo":"endereco.cep","mensagem":"must match \"\\d{5}-\\d{3}\""}]
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
2023-09-18T11:41:47.731-03:00  INFO 15940 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2023-09-18T11:41:47.734-03:00  INFO 15940 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2023-09-18T11:41:47.743-03:00  INFO 15940 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

Manda aqui a sua classe TratadorDeErros

@RestControllerAdvice
public class TratadorDeErros {

    @ExceptionHandler(EntityNotFoundException.class)
    public ResponseEntity<Void> trataErro404() {
        return ResponseEntity.notFound().build();
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<List<DadosErroValidacao>> trataErro400(MethodArgumentNotValidException ex) {
        List<FieldError> erros = ex.getFieldErrors();

        return ResponseEntity.badRequest().body(erros.stream().map(DadosErroValidacao::new).toList());
    }

    @ExceptionHandler(BadCredentialsException.class)
    public ResponseEntity<String> trataErroBadCredentials() {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Credenciais inválidas");
    }

    @ExceptionHandler(AuthenticationException.class)
    public ResponseEntity<String> trataErroAuthentication() {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Falha na autenticação");
    }

    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity<String> trataErroAcessoNegado() {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Acesso negado");
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<?> trataErro500(Exception ex) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Erro: " + ex.getLocalizedMessage());
    }

    @ExceptionHandler(ValidacaoException.class)
    public ResponseEntity<?> trataErroRegraDeNegocio(ValidacaoException ex) {
        return ResponseEntity.badRequest().body(ex.getMessage());
    }

    private record DadosErroValidacao(String campo, String mensagem) {
        public DadosErroValidacao(FieldError erro) {
            this(erro.getField(), erro.getDefaultMessage());
        }
    }

}
solução!

Está caindo nesse seu método:

@ExceptionHandler(Exception.class)
public ResponseEntity<?> trataErro500(Exception ex) {
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Erro: " + ex.getLocalizedMessage());
}

E por isso retorna erro 500.

Faltou esse método na sua classe:

@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity tratarErro400(HttpMessageNotReadableException ex) {
    return ResponseEntity.badRequest().body(ex.getMessage());
}