Solucionado (ver solução)

Importante

Você está vendo a versão anterior da nova experiência da Alura que estamos preparando para você. Em breve, ela ganha uma identidade visual novinha totalmente pensada em potencializar seus estudos!

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());
}