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

[Dúvida] Testes Update

Olá! Estou com dúvidas para fazer testes para métodos update. Eu tentei testar estes cenários, tanto de erro, como de sucesso, está retornando erro 500, ao invés dos códigos que selecionei (200 e 404). Para o erro 404, eu tentei criar, colocando um id inesperado, tentando simular que não existe, mas retorna nulo. O mesmo processo está acontecendo, quando estou fazendo para o cenário de sucesso. Vou te passar os códigos que criei como estou fazendo, junto com o método controller, que precisa ser testado. Poderiam me ajudar, por favor?

@RestController
@RequestMapping("beneficiaries")
@SecurityRequirement(name = "bearer-key")
public class BeneficiarioController {

    @PutMapping
    @Transactional
    public ResponseEntity updateBeneficiary(@RequestBody @Valid DadosAtualizacaoBeneficiario dados) {
        var beneficiario = beneficiarioRepository.getReferenceById(dados.id());
        beneficiario.atualizarInformacoes(dados);

        return ResponseEntity.ok(new DadosDetalhamentoBeneficiario(beneficiario));
    }

}
@Test
    @DisplayName("Deveria devolver código http 404, quando as informações estão inválidas")
    @WithMockUser
    void updateBeneficiaryCenario1() throws Exception {
        Long id = 2L;

        var repository = beneficiarioRepository;
        repository.getReferenceById(id);

        when(repository.getReferenceById(anyLong())).thenThrow(EntityNotFoundException.class);

        // simulando que o id não existe
//        given(repository).willThrow(EntityNotFoundException.class);

        var response = mvc
                .perform(
                        put("/beneficiaries/{id}", id)
                )
                .andReturn().getResponse();

        assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value());
    }
@Test
    @DisplayName("Deveria devolver código http 200, quando as informações estiverem válidas")
    @WithMockUser
    void updateBeneficiaryCenario2() throws Exception {
        Long id = 1L;

        DadosAtualizacaoDocumento dadosAtualizacaoDocumento = new DadosAtualizacaoDocumento(
                5L,
                TipoDocumento.CARTEIRA_IDENTIDADE,
                "43789563",
                LocalDate.parse("2019-09-18"),
                "Test description",
                LocalDate.parse("2024-04-16"),
                LocalDate.parse("2024-04-16"));

        DadosEndereco dadosEndereco = new DadosEndereco("Rua xpto", "Bairro", "12345-789", "Cidade", "SP", "Casa", "789");

        DadosAtualizacaoBeneficiario dadosAtualizacaoBeneficiario = new DadosAtualizacaoBeneficiario(
                null,
                "Nome",
                "112345678",
                dadosEndereco,
                Collections.singletonList(dadosAtualizacaoDocumento)
        );

        // considerando que sua classe Endereco tenha um construtor que receba um objeto DadosEndereco:
        var enderecoEsperado = new Endereco(dadosAtualizacaoBeneficiario.endereco());

        // considerando que sua classe DadosDetalhamentoDocumento tenha um construtor que receba uma lista DadosCadastroDocumento:
        List<DadosDetalhamentoDocumento> documentosEsperados = dadosAtualizacaoBeneficiario.documentos().stream().map(DadosDetalhamentoDocumento::new).toList();

        DadosDetalhamentoBeneficiario dadosDetalhamentoBeneficiario = new DadosDetalhamentoBeneficiario(
                null,
                dadosAtualizacaoBeneficiario.nome(),
                dadosAtualizacaoBeneficiario.telefone(),
                null,
                null,
                null,
                documentosEsperados,
                enderecoEsperado);

        var response = mvc
                .perform(
                        put("/beneficiaries/{id}", id)
                                .contentType(MediaType.APPLICATION_JSON)
                                .content(dadosAtualizacaoBeneficiarioJson.write(dadosAtualizacaoBeneficiario).getJson())
                )
                .andReturn().getResponse();

        assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());

        String jsonEsperado = dadosDetalhamentoBeneficiarioJson.write(dadosDetalhamentoBeneficiario).getJson();

        assertThat(response.getContentAsString()).isEqualTo(jsonEsperado);
    }
12 respostas
MockHttpServletRequest:
      HTTP Method = PUT
      Request URI = /beneficiaries/1
       Parameters = {}
          Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"386"]
             Body = {"id":null,"nome":"Nome","telefone":"112345678","endereco":{"logradouro":"Rua xpto","bairro":"Bairro","cep":"12345-789","cidade":"Cidade","uf":"SP","complemento":"Casa","numero":"789"},"documentos":[{"id":5,"tipoDocumento":"CARTEIRA_IDENTIDADE","numero":"43789563","dataExpedicao":"2019-09-18","descricao":"Test description","dataInclusao":"2024-04-16","dataAtualizacao":"2024-04-16"}]}
    Session Attrs = {}

Handler:
             Type = null

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = org.springframework.web.HttpRequestMethodNotSupportedException

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

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 500
    Error message = null
          Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", Content-Type:"text/plain;charset=UTF-8", Content-Length:"44", 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"]
     Content type = text/plain;charset=UTF-8
             Body = Error: Request method 'PUT' is not supported
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

org.opentest4j.AssertionFailedError: 
expected: 200
 but was: 500
Expected :200
Actual   :500
<Click to see difference>

    at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:67)
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500)
    at com.plano.saude.cadastro.controller.BeneficiarioControllerTest.updateBeneficiaryCenario2(BeneficiarioControllerTest.java:232)
    at java.base/java.lang.reflect.Method.invoke(Method.java:578)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

Olá, Alexandre! Tudo bem?

Primeiramente, no seu teste para o cenário de erro 404, você mencionou que está tentando simular um ID que não existe, mas o retorno é nulo. Isso pode estar acontecendo porque o método getReferenceById do JPA não lança EntityNotFoundException imediatamente ao ser chamado.

Em vez disso, ele retorna uma referência proxy ao objeto, e a exceção é lançada apenas quando você tenta acessar alguma propriedade desse objeto. Para garantir que a exceção seja lançada no seu teste, você pode modificar o método no seu controller para acessar uma propriedade do objeto beneficiario logo após obtê-lo, algo como beneficiario.getId(), que forçará a carga e, consequentemente, a exceção, se o ID não existir.

Aqui está uma sugestão de como modificar seu método no controller:

@RestController
@RequestMapping("beneficiaries")
@SecurityRequirement(name = "bearer-key")
public class BeneficiarioController {

    @PutMapping
    @Transactional
    public ResponseEntity updateBeneficiary(@RequestBody @Valid DadosAtualizacaoBeneficiario dados) {
        try {
            var beneficiario = beneficiarioRepository.getReferenceById(dados.id());
            beneficiario.getId(); // Força o acesso ao banco para verificar se o beneficiário existe
            beneficiario.atualizarInformacoes(dados);
            return ResponseEntity.ok(new DadosDetalhamentoBeneficiario(beneficiario));
        } catch (EntityNotFoundException e) {
            return ResponseEntity.notFound().build();
        }
    }
}

No seu teste, você já está simulando corretamente a exceção usando o Mockito, então essa parte parece estar correta.

Quanto ao teste de sucesso, parece que você está no caminho certo, mas certifique-se de que o objeto dadosAtualizacaoBeneficiarioJson esteja corretamente configurado para serializar o objeto dadosAtualizacaoBeneficiario para JSON. Se você estiver recebendo um erro de 500, pode ser um problema na serialização ou na lógica dentro do método atualizarInformacoes.

Verifique se todas as dependências e objetos estão sendo corretamente mockados/injetados e se não há nenhuma operação que possa lançar uma exceção não tratada.

Espero que essas sugestões ajudem a resolver os problemas que você está enfrentando com os testes. Continue explorando e ajustando conforme necessário.

Bons estudos!

Caso este post tenha lhe ajudado, por favor, marcar como solucionado ✓.

Oi!

No seu teste você está disparando uma requisição PUT para a url /beneficiaries/{id}, mas no seu controller não existe essa url.

No seu controller o id não é recebido na URL e sim no corpo da requisição e por isso deu erro no teste.

Olá! Concordo, é verdade. A url não existe, passando dessa forma. Eu sei que, para retornar erro 404, basta eu passar um id que não exista no banco, porque não vai encontrar. Se deu erro no teste que estou fazendo, por causa do endpoint, então eu terei que montar um teste, parecido com uma requisição POST, passando um id inexistente, mas disparando uma requisição PUT, para a url /beneficiaries. Fazendo dessa forma, então, os cenários de erro e sucesso conseguem ser testados, correto?

Isso. A requisição deve ser PUT para a url /beneficiaries e no teste você deve enviar um objeto DadosAtualizacaoBeneficiario com um id qualquer.

Bom. No teste de sucesso, não sei o que está acontecendo, porque enviei um objeto DadosAtualizacaoBeneficiario, com um id qualquer que eu selecionei. A requisição até retorna 200, aparece no stacktrace, mas quando eu comparo, tem essas diferenças que eu não consigo deixar iguais as que eu estou passando no retorno, ajustando o teste dessa forma. Como que eu faço para deixar iguais os dados esperados e os atuais? Insira aqui a descrição dessa imagem para ajudar na acessibilidade

@Test
    @DisplayName("Deveria devolver código http 200, quando as informações estiverem válidas")
    @WithMockUser
    void updateBeneficiaryCenario2() throws Exception {

        DadosAtualizacaoDocumento dadosAtualizacaoDocumento = new DadosAtualizacaoDocumento(
                null,
                TipoDocumento.CARTEIRA_IDENTIDADE,
                "43789563",
                LocalDate.parse("2019-09-18"),
                "Test description",
                LocalDate.parse("2024-04-16"),
                LocalDate.parse("2024-04-17"));

        DadosEndereco dadosEndereco = new DadosEndereco("Rua Morada do Beneficiario", "Bairro", "12345-789", "Cidade", "SP", "Casa", "789");

        DadosAtualizacaoBeneficiario dadosAtualizacaoBeneficiario = new DadosAtualizacaoBeneficiario(
                null,
                "Nome do Beneficiario",
                "43789563",
                dadosEndereco,
                Collections.singletonList(dadosAtualizacaoDocumento)
        );

        // considerando que sua classe Endereco tenha um construtor que receba um objeto DadosEndereco:
        var enderecoEsperado = new Endereco(dadosAtualizacaoBeneficiario.endereco());

        // considerando que sua classe DadosDetalhamentoDocumento tenha um construtor que receba uma lista DadosCadastroDocumento:
        List<DadosDetalhamentoDocumento> documentosEsperados = dadosAtualizacaoBeneficiario.documentos().stream().map(DadosDetalhamentoDocumento::new).toList();

        DadosDetalhamentoBeneficiario dadosDetalhamentoBeneficiario = new DadosDetalhamentoBeneficiario(
                null,
                dadosAtualizacaoBeneficiario.nome(),
                dadosAtualizacaoBeneficiario.telefone(),
                null,
                null,
                null,
                documentosEsperados,
                enderecoEsperado);

        // simulando que o id existe
        when(beneficiarioRepository.getReferenceById(any())).thenReturn(new Beneficiario(dadosDetalhamentoBeneficiario));

        var response = mvc
                .perform(
                        put("/beneficiaries")
                                .contentType(MediaType.APPLICATION_JSON)
                                .content(dadosAtualizacaoBeneficiarioJson.write(dadosAtualizacaoBeneficiario).getJson())
                )
                .andReturn().getResponse();

        assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());

        String jsonEsperado = dadosDetalhamentoBeneficiarioJson.write(dadosDetalhamentoBeneficiario).getJson();

        assertThat(response.getContentAsString()).isEqualTo(jsonEsperado);
    }

O cenário de erro, com os ajustes que eu fiz, funcionou, certinho.

A diferença no json é na parte de documentos. Talvez o problema esteja nesse método:

beneficiario.atualizarInformacoes(dados);

Verifica se esse método atualizarInformacoes está setando os documentos corretamente, de acordo com o parâmetro dados.

Este método está setando os documentos dessa forma, pegando de um listIterator. Talvez seja aqui o problema e não tinha percebido. Insira aqui a descrição dessa imagem para ajudar na acessibilidade Até alterei, colocando um forEach no lugar, mas o erro continua, quando chama este método

this.documentos.forEach(documento -> documento.setBeneficiario(this));

Vai precisar converter os objetos. Algo como:

if (dados.documentos() != null) {
    this.documentos = dados.documentos().stream().map(Documento::new).toList();
}

Documento::new você altera para ficar de acordo com o tipo do atributo na sua entidade

Não funcionou, convertendo os objetos, mesmo fazendo algo como: Na classe Beneficiario:

if (dados.documentos() != null){
    this.documentos = dados.documentos().stream().map(Documento::new).toList();
    this.documentos.forEach(d -> d.setBeneficiario(this));  // Esta linha eu coloquei depois só para testar
}

Na classe Documento:

public Documento(DadosAtualizacaoDocumento dadosAtualizacaoDocumento) {
}

Na classe (método) de teste:

@Test
    @DisplayName("Deveria devolver código http 200, quando as informações estiverem válidas")
    @WithMockUser
    void updateBeneficiaryCenario2() throws Exception {

        DadosAtualizacaoDocumento dadosAtualizacaoDocumento = new DadosAtualizacaoDocumento(
                null,
                TipoDocumento.CARTEIRA_IDENTIDADE,
                "43789563",
                LocalDate.parse("2019-09-18"),
                "Test description",
                LocalDate.parse("2024-04-16"),
                LocalDate.parse("2024-04-17"));

        DadosEndereco dadosEndereco = new DadosEndereco("Rua Morada do Beneficiario", "Bairro", "12345-789", "Cidade", "SP", "Casa", "789");

        DadosAtualizacaoBeneficiario dadosAtualizacaoBeneficiario = new DadosAtualizacaoBeneficiario(
                null,
                "Nome do Beneficiario",
                "43789563",
                dadosEndereco,
                Collections.singletonList(dadosAtualizacaoDocumento)
        );

        // considerando que sua classe Endereco tenha um construtor que receba um objeto DadosEndereco:
        var enderecoEsperado = new Endereco(dadosAtualizacaoBeneficiario.endereco());

        // considerando que sua classe DadosDetalhamentoDocumento tenha um construtor que receba uma lista DadosCadastroDocumento:
        List<DadosDetalhamentoDocumento> documentosEsperados = Collections.singletonList(new DadosDetalhamentoDocumento(
                null,
                TipoDocumento.CARTEIRA_DE_TRABALHO,
                "12345678910",
                LocalDate.parse("2000-03-03"),
                "Test descrp",
                LocalDate.parse("2024-03-03"),
                LocalDate.parse("2024-03-03")
        ));
//        List<DadosDetalhamentoDocumento> documentosEsperados = dadosAtualizacaoBeneficiario.documentos().stream().map(DadosDetalhamentoDocumento::new).toList();

        DadosDetalhamentoBeneficiario dadosDetalhamentoBeneficiario = new DadosDetalhamentoBeneficiario(
                1L,
                dadosAtualizacaoBeneficiario.nome(),
                dadosAtualizacaoBeneficiario.telefone(),
                null,
                null,
                null,
                documentosEsperados,
                enderecoEsperado);

        // simulando que o id existe
        when(beneficiarioRepository.getReferenceById(any())).thenReturn(new Beneficiario(dadosDetalhamentoBeneficiario));

        var response = mvc
                .perform(
                        put("/beneficiaries")
                                .contentType(MediaType.APPLICATION_JSON)
                                .content(dadosAtualizacaoBeneficiarioJson.write(dadosAtualizacaoBeneficiario).getJson())
                )
                .andReturn().getResponse();

        assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());

        String jsonEsperado = dadosDetalhamentoBeneficiarioJson.write(dadosDetalhamentoBeneficiario).getJson();

        assertThat(response.getContentAsString()).isEqualTo(jsonEsperado);
    }
solução!

O construtor da classe Documento está vazio. Precisa setar os atributos de acordo com o parâmetro dadosAtualizacaoDocumento

É verdade. Setei os atributos de acordo com os parâmetros dadosAtualizacaoDocumento e dadosDetalhamentoDocumento que também estava vazio no construtor na classe Documento. Funcionou!