Mockito
No capítulo anterior, revisamos como escrever testes unitários. No entanto, em nossa aplicação, isso nem sempre é conveniente, pois testar um controller exige testar o service, e testar o service requer testar o repository.
Essa cadeia de dependências levanta a questão: Precisamos testar todos os módulos e suas dependências, ou podemos simular seu comportamento — em outras palavras, mocká-los?
Mockito: Introdução
Mockito é uma biblioteca popular para Java utilizada para criar objetos mock em testes unitários.
Ela permite que desenvolvedores simulem o comportamento de dependências externas complexas (como bancos de dados, serviços ou APIs) para isolar e testar a lógica de um componente específico (geralmente uma classe ou método) sem interagir com esses sistemas externos.
Isso torna o teste unitário mais previsível e rápido, pois não há necessidade de chamadas reais a recursos externos durante os testes.
Objetos Mock no Mockito
Isso permite que desenvolvedores garantam que o componente sendo testado (a unidade) se comporte corretamente sem depender de dependências reais como bancos de dados ou serviços de terceiros.
Uma classe pode ser anotada com @Mock para criar sua versão mock. Essa anotação é frequentemente utilizada junto com @InjectMocks para injetar automaticamente as dependências na classe sob teste.
@Mock
private MyService myServiceMock;
Este objeto mock pode então ser injetado como uma dependência em outro objeto usando a anotação @InjectMocks. Esta anotação injeta automaticamente os objetos mock na classe que está sendo testada.
@InjectMocks
private MyController myController;
Métodos Principais no Mockito
O Mockito oferece uma ampla variedade de métodos para gerenciar objetos mock, cada um com um propósito específico no processo de teste. Abaixo estão os métodos mais importantes juntamente com suas explicações.
Testando Controladores com Mockito
Testar controladores frequentemente envolve o uso de mocks para serviços que são chamados dentro do controlador. Nesse contexto, o Mockito desempenha um papel fundamental ao ajudar a isolar a lógica do controlador da camada de serviço.
Resumo Rápido do Vídeo
Testamos controladores utilizando a classe MockMvc, mas o que ela oferece e quais são os benefícios de utilizá-la?
@Autowired
private MockMvc mockMvc;
Com o MockMvc, é possível simular diversas requisições HTTP (GET, POST, PUT, etc.), passar parâmetros, headers e verificar respostas, códigos de status, headers e corpos de resposta, tornando o teste unitário de controladores muito mais simples.
Por exemplo, é possível utilizar métodos como perform() para executar a requisição, andExpect() para verificar os resultados esperados e content() para conferir o conteúdo da resposta.
Test.java
1234mockMvc.perform(put("/books/{id}", bookId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookRequestDTO))) .andExpect(status().isOk())
Além disso, é possível encadear asserções para verificar o status HTTP usando status().isOk(), conferir estruturas de resposta JSON com jsonPath(), entre outros.
Test.java
1234.andExpect(jsonPath("$.id").value(bookResponseDTO.getId())) .andExpect(jsonPath("$.name").value(bookResponseDTO.getName())) .andExpect(jsonPath("$.author").value(bookResponseDTO.getAuthor())) .andExpect(jsonPath("$.price").value(bookResponseDTO.getPrice()));
Essas ferramentas possibilitam testes abrangentes do comportamento do controller sem a necessidade de realizar chamadas HTTP reais.
Anotações Principais
A anotação @WebMvcTest(BookController.class) é utilizada para testar o BookController, carregando apenas os componentes necessários para esse controlador específico. Ela exclui o restante da infraestrutura do Spring, permitindo foco no teste do próprio controlador.
@WebMvcTest(BookController.class)
public class BookControllerTest
Além disso, a anotação @MockBean aplicada ao campo bookService cria uma versão simulada do serviço, permitindo simular seu comportamento. Isso ajuda a isolar o controlador de suas dependências e focar na testagem de sua lógica.
@MockBean
private BookService bookService;
Testes para o método updateBook
Foram escritos dois testes para o método updateBook que cobrem todos os casos possíveis para este método. No primeiro teste, é verificado se tudo foi bem-sucedido e se a entidade foi atualizada.
BookControllerTest.java
1234567891011121314151617@Test void testUpdateBook_whenBookExists_shouldReturnUpdatedBook() throws Exception { String bookId = "1"; when(bookService.updateBook(bookId, bookRequestDTO)).thenReturn(bookResponseDTO); mockMvc.perform(put("/books/{id}", bookId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookRequestDTO))) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(bookResponseDTO.getId())) .andExpect(jsonPath("$.name").value(bookResponseDTO.getName())) .andExpect(jsonPath("$.author").value(bookResponseDTO.getAuthor())) .andExpect(jsonPath("$.price").value(bookResponseDTO.getPrice())); verify(bookService).updateBook(bookId, bookRequestDTO); }
Este teste verifica a atualização bem-sucedida de um book caso ele exista no banco de dados. O método updateBook() do serviço é chamado com o Id do livro e um objeto bookRequestDTO, após o qual ele retorna um objeto bookResponseDTO representando o livro atualizado.
Verificação de Tratamento de Exceção
Também há um teste em que ocorre uma exceção no método updateBook, sendo necessário verificar como o controller se comporta nessa situação.
BookControllerTest.java
12345678910111213141516@Test void testUpdateBook_whenBookNotFound_shouldReturnApiException() throws Exception { String bookId = "1"; String errorMessage = "ID not found"; when(bookService.updateBook(bookId, bookRequestDTO)) .thenThrow(new ApiException(errorMessage, HttpStatus.NOT_FOUND)); mockMvc.perform(put("/books/{id}", bookId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookRequestDTO))) .andExpect(status().isNotFound()) .andExpect(jsonPath("$.error").value(errorMessage)); verify(bookService).updateBook(bookId, bookRequestDTO); }
Este teste verifica o comportamento do controller ao tentar atualizar um livro que não foi encontrado no banco de dados. O método updateBook() do serviço é chamado primeiro com o Id do livro e um objeto bookRequestDTO.
No entanto, em vez de um resultado bem-sucedido, o serviço lança uma ApiException, indicando que o livro com o Id fornecido não foi encontrado.
Teste de Serviço com Mockito
Ao testar um serviço, é importante isolá-lo de dependências como repositórios ou APIs externas. O Mockito permite criar mocks dessas dependências e definir seu comportamento nos testes.
Resumo breve do vídeo
Vamos começar testando nosso método quando ele atualiza com sucesso os dados, ou seja, quando o Id é válido. Obviamente, usaremos o Mockito para simular o comportamento do repositório.
BookServiceTest.java
123456789101112131415161718192021222324252627282930313233@Test void testUpdateBook_whenBookExists_shouldUpdateAndReturnBook() { BookRequestDTO bookRequestDTO = new BookRequestDTO(); bookRequestDTO.setName("Updated Name"); bookRequestDTO.setAuthor("Updated Author"); bookRequestDTO.setPrice("150"); Book bookWithRepository = new Book(); bookWithRepository.setId("1"); bookWithRepository.setName("Original Name"); bookWithRepository.setAuthor("Original Author"); bookWithRepository.setPrice("100"); Book updateBook = new Book(); updateBook.setId("1"); updateBook.setName("Updated Name"); updateBook.setAuthor("Updated Author"); updateBook.setPrice("150"); when(bookRepository.findById("1")).thenReturn(Optional.of(bookWithRepository)); when(bookRepository.save(bookWithRepository)).thenReturn(updateBook); BookResponseDTO result = bookService.updateBook("1", bookRequestDTO); assertNotNull(result); assertEquals("1", result.getId()); assertEquals("Updated Name", result.getName()); assertEquals("Updated Author", result.getAuthor()); assertEquals("150", result.getPrice()); verify(bookRepository).findById("1"); verify(bookRepository).save(bookWithRepository); }
O método bookRepository.findById("1") é simulado para retornar um Book existente do repositório, e bookRepository.save(bookWithRepository) é simulado para retornar o livro atualizado após salvar as alterações.
O método service updateBook() é chamado para atualizar o livro com base no bookRequestDTO, retornando um BookResponseDTO.
assertNotNull(result) garante que o resultado não seja null, indicando uma atualização bem-sucedida, enquanto assertEquals() verifica se o ID, name, author e price do livro atualizado correspondem aos valores esperados.
assertNotNull(result);
assertEquals("1", result.getId());
assertEquals("Updated Name", result.getName());
assertEquals("Updated Author", result.getAuthor());
assertEquals("150", result.getPrice());
Por fim, verify() garante que findById() e save() no repositório foram chamados com os parâmetros corretos.
verify(bookRepository).findById("1");
verify(bookRepository).save(bookWithRepository);
Verificação de uma Exceção no Serviço
Este teste verifica se o serviço lança uma ApiException caso o Book a ser atualizado não seja encontrado no banco de dados.
BookServiceTest.java
1234567891011121314151617181920@Test void testUpdateBook_whenBookNotFound_shouldThrowApiException() { BookRequestDTO bookRequestDTO = new BookRequestDTO(); bookRequestDTO.setName("Updated Name"); bookRequestDTO.setAuthor("Updated Author"); bookRequestDTO.setPrice("150"); String idTest = "999"; when(bookRepository.findById(idTest)).thenReturn(Optional.empty()); ApiException apiException = assertThrows(ApiException.class, () -> { bookService.updateBook(idTest, bookRequestDTO); }); assertEquals("Not found book by id: " + idTest, apiException.getMessage()); assertEquals(HttpStatus.NOT_FOUND, apiException.getHttpStatus()); verify(bookRepository, never()).save(any(Book.class)); }
Inicialmente, um objeto BookRequestDTO é criado com os dados para a atualização, e um Id de livro de teste que não existe no banco de dados "999" é atribuído. O método bookRepository.findById(idTest) é mockado para retornar Optional.empty(), indicando que não existe livro com esse Id.
when(bookRepository.findById(idTest)).thenReturn(Optional.empty());
O teste utiliza o método assertThrows() para verificar que, ao chamar bookService.updateBook(idTest, bookRequestDTO), uma ApiException é lançada.
Em seguida, os métodos assertEquals() verificam se a mensagem da exceção corresponde ao texto esperado e se o status do erro é igual a HttpStatus.NOT_FOUND.
ApiException apiException = assertThrows(ApiException.class, () -> {
bookService.updateBook(idTest, bookRequestDTO);
});
assertEquals("Not found book by id: " + idTest, apiException.getMessage());
assertEquals(HttpStatus.NOT_FOUND, apiException.getHttpStatus());
O método verify(bookRepository, never()).save(any(Book.class)) garante que o método de salvar livro save() não foi chamado, pois o livro não foi encontrado e a atualização não foi realizada.
verify(bookRepository, never()).save(any(Book.class));
Resumo
Mockito é uma biblioteca para criação de objetos mock que permite simular o comportamento de dependências e isolar o código em teste. Isso simplifica os testes, tornando-os mais rápidos, mais estáveis e independentes de recursos externos.
Obrigado pelo seu feedback!
Pergunte à IA
Pergunte à IA
Pergunte o que quiser ou experimente uma das perguntas sugeridas para iniciar nosso bate-papo
Awesome!
Completion rate improved to 3.45
Mockito
Deslize para mostrar o menu
No capítulo anterior, revisamos como escrever testes unitários. No entanto, em nossa aplicação, isso nem sempre é conveniente, pois testar um controller exige testar o service, e testar o service requer testar o repository.
Essa cadeia de dependências levanta a questão: Precisamos testar todos os módulos e suas dependências, ou podemos simular seu comportamento — em outras palavras, mocká-los?
Mockito: Introdução
Mockito é uma biblioteca popular para Java utilizada para criar objetos mock em testes unitários.
Ela permite que desenvolvedores simulem o comportamento de dependências externas complexas (como bancos de dados, serviços ou APIs) para isolar e testar a lógica de um componente específico (geralmente uma classe ou método) sem interagir com esses sistemas externos.
Isso torna o teste unitário mais previsível e rápido, pois não há necessidade de chamadas reais a recursos externos durante os testes.
Objetos Mock no Mockito
Isso permite que desenvolvedores garantam que o componente sendo testado (a unidade) se comporte corretamente sem depender de dependências reais como bancos de dados ou serviços de terceiros.
Uma classe pode ser anotada com @Mock para criar sua versão mock. Essa anotação é frequentemente utilizada junto com @InjectMocks para injetar automaticamente as dependências na classe sob teste.
@Mock
private MyService myServiceMock;
Este objeto mock pode então ser injetado como uma dependência em outro objeto usando a anotação @InjectMocks. Esta anotação injeta automaticamente os objetos mock na classe que está sendo testada.
@InjectMocks
private MyController myController;
Métodos Principais no Mockito
O Mockito oferece uma ampla variedade de métodos para gerenciar objetos mock, cada um com um propósito específico no processo de teste. Abaixo estão os métodos mais importantes juntamente com suas explicações.
Testando Controladores com Mockito
Testar controladores frequentemente envolve o uso de mocks para serviços que são chamados dentro do controlador. Nesse contexto, o Mockito desempenha um papel fundamental ao ajudar a isolar a lógica do controlador da camada de serviço.
Resumo Rápido do Vídeo
Testamos controladores utilizando a classe MockMvc, mas o que ela oferece e quais são os benefícios de utilizá-la?
@Autowired
private MockMvc mockMvc;
Com o MockMvc, é possível simular diversas requisições HTTP (GET, POST, PUT, etc.), passar parâmetros, headers e verificar respostas, códigos de status, headers e corpos de resposta, tornando o teste unitário de controladores muito mais simples.
Por exemplo, é possível utilizar métodos como perform() para executar a requisição, andExpect() para verificar os resultados esperados e content() para conferir o conteúdo da resposta.
Test.java
1234mockMvc.perform(put("/books/{id}", bookId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookRequestDTO))) .andExpect(status().isOk())
Além disso, é possível encadear asserções para verificar o status HTTP usando status().isOk(), conferir estruturas de resposta JSON com jsonPath(), entre outros.
Test.java
1234.andExpect(jsonPath("$.id").value(bookResponseDTO.getId())) .andExpect(jsonPath("$.name").value(bookResponseDTO.getName())) .andExpect(jsonPath("$.author").value(bookResponseDTO.getAuthor())) .andExpect(jsonPath("$.price").value(bookResponseDTO.getPrice()));
Essas ferramentas possibilitam testes abrangentes do comportamento do controller sem a necessidade de realizar chamadas HTTP reais.
Anotações Principais
A anotação @WebMvcTest(BookController.class) é utilizada para testar o BookController, carregando apenas os componentes necessários para esse controlador específico. Ela exclui o restante da infraestrutura do Spring, permitindo foco no teste do próprio controlador.
@WebMvcTest(BookController.class)
public class BookControllerTest
Além disso, a anotação @MockBean aplicada ao campo bookService cria uma versão simulada do serviço, permitindo simular seu comportamento. Isso ajuda a isolar o controlador de suas dependências e focar na testagem de sua lógica.
@MockBean
private BookService bookService;
Testes para o método updateBook
Foram escritos dois testes para o método updateBook que cobrem todos os casos possíveis para este método. No primeiro teste, é verificado se tudo foi bem-sucedido e se a entidade foi atualizada.
BookControllerTest.java
1234567891011121314151617@Test void testUpdateBook_whenBookExists_shouldReturnUpdatedBook() throws Exception { String bookId = "1"; when(bookService.updateBook(bookId, bookRequestDTO)).thenReturn(bookResponseDTO); mockMvc.perform(put("/books/{id}", bookId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookRequestDTO))) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(bookResponseDTO.getId())) .andExpect(jsonPath("$.name").value(bookResponseDTO.getName())) .andExpect(jsonPath("$.author").value(bookResponseDTO.getAuthor())) .andExpect(jsonPath("$.price").value(bookResponseDTO.getPrice())); verify(bookService).updateBook(bookId, bookRequestDTO); }
Este teste verifica a atualização bem-sucedida de um book caso ele exista no banco de dados. O método updateBook() do serviço é chamado com o Id do livro e um objeto bookRequestDTO, após o qual ele retorna um objeto bookResponseDTO representando o livro atualizado.
Verificação de Tratamento de Exceção
Também há um teste em que ocorre uma exceção no método updateBook, sendo necessário verificar como o controller se comporta nessa situação.
BookControllerTest.java
12345678910111213141516@Test void testUpdateBook_whenBookNotFound_shouldReturnApiException() throws Exception { String bookId = "1"; String errorMessage = "ID not found"; when(bookService.updateBook(bookId, bookRequestDTO)) .thenThrow(new ApiException(errorMessage, HttpStatus.NOT_FOUND)); mockMvc.perform(put("/books/{id}", bookId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookRequestDTO))) .andExpect(status().isNotFound()) .andExpect(jsonPath("$.error").value(errorMessage)); verify(bookService).updateBook(bookId, bookRequestDTO); }
Este teste verifica o comportamento do controller ao tentar atualizar um livro que não foi encontrado no banco de dados. O método updateBook() do serviço é chamado primeiro com o Id do livro e um objeto bookRequestDTO.
No entanto, em vez de um resultado bem-sucedido, o serviço lança uma ApiException, indicando que o livro com o Id fornecido não foi encontrado.
Teste de Serviço com Mockito
Ao testar um serviço, é importante isolá-lo de dependências como repositórios ou APIs externas. O Mockito permite criar mocks dessas dependências e definir seu comportamento nos testes.
Resumo breve do vídeo
Vamos começar testando nosso método quando ele atualiza com sucesso os dados, ou seja, quando o Id é válido. Obviamente, usaremos o Mockito para simular o comportamento do repositório.
BookServiceTest.java
123456789101112131415161718192021222324252627282930313233@Test void testUpdateBook_whenBookExists_shouldUpdateAndReturnBook() { BookRequestDTO bookRequestDTO = new BookRequestDTO(); bookRequestDTO.setName("Updated Name"); bookRequestDTO.setAuthor("Updated Author"); bookRequestDTO.setPrice("150"); Book bookWithRepository = new Book(); bookWithRepository.setId("1"); bookWithRepository.setName("Original Name"); bookWithRepository.setAuthor("Original Author"); bookWithRepository.setPrice("100"); Book updateBook = new Book(); updateBook.setId("1"); updateBook.setName("Updated Name"); updateBook.setAuthor("Updated Author"); updateBook.setPrice("150"); when(bookRepository.findById("1")).thenReturn(Optional.of(bookWithRepository)); when(bookRepository.save(bookWithRepository)).thenReturn(updateBook); BookResponseDTO result = bookService.updateBook("1", bookRequestDTO); assertNotNull(result); assertEquals("1", result.getId()); assertEquals("Updated Name", result.getName()); assertEquals("Updated Author", result.getAuthor()); assertEquals("150", result.getPrice()); verify(bookRepository).findById("1"); verify(bookRepository).save(bookWithRepository); }
O método bookRepository.findById("1") é simulado para retornar um Book existente do repositório, e bookRepository.save(bookWithRepository) é simulado para retornar o livro atualizado após salvar as alterações.
O método service updateBook() é chamado para atualizar o livro com base no bookRequestDTO, retornando um BookResponseDTO.
assertNotNull(result) garante que o resultado não seja null, indicando uma atualização bem-sucedida, enquanto assertEquals() verifica se o ID, name, author e price do livro atualizado correspondem aos valores esperados.
assertNotNull(result);
assertEquals("1", result.getId());
assertEquals("Updated Name", result.getName());
assertEquals("Updated Author", result.getAuthor());
assertEquals("150", result.getPrice());
Por fim, verify() garante que findById() e save() no repositório foram chamados com os parâmetros corretos.
verify(bookRepository).findById("1");
verify(bookRepository).save(bookWithRepository);
Verificação de uma Exceção no Serviço
Este teste verifica se o serviço lança uma ApiException caso o Book a ser atualizado não seja encontrado no banco de dados.
BookServiceTest.java
1234567891011121314151617181920@Test void testUpdateBook_whenBookNotFound_shouldThrowApiException() { BookRequestDTO bookRequestDTO = new BookRequestDTO(); bookRequestDTO.setName("Updated Name"); bookRequestDTO.setAuthor("Updated Author"); bookRequestDTO.setPrice("150"); String idTest = "999"; when(bookRepository.findById(idTest)).thenReturn(Optional.empty()); ApiException apiException = assertThrows(ApiException.class, () -> { bookService.updateBook(idTest, bookRequestDTO); }); assertEquals("Not found book by id: " + idTest, apiException.getMessage()); assertEquals(HttpStatus.NOT_FOUND, apiException.getHttpStatus()); verify(bookRepository, never()).save(any(Book.class)); }
Inicialmente, um objeto BookRequestDTO é criado com os dados para a atualização, e um Id de livro de teste que não existe no banco de dados "999" é atribuído. O método bookRepository.findById(idTest) é mockado para retornar Optional.empty(), indicando que não existe livro com esse Id.
when(bookRepository.findById(idTest)).thenReturn(Optional.empty());
O teste utiliza o método assertThrows() para verificar que, ao chamar bookService.updateBook(idTest, bookRequestDTO), uma ApiException é lançada.
Em seguida, os métodos assertEquals() verificam se a mensagem da exceção corresponde ao texto esperado e se o status do erro é igual a HttpStatus.NOT_FOUND.
ApiException apiException = assertThrows(ApiException.class, () -> {
bookService.updateBook(idTest, bookRequestDTO);
});
assertEquals("Not found book by id: " + idTest, apiException.getMessage());
assertEquals(HttpStatus.NOT_FOUND, apiException.getHttpStatus());
O método verify(bookRepository, never()).save(any(Book.class)) garante que o método de salvar livro save() não foi chamado, pois o livro não foi encontrado e a atualização não foi realizada.
verify(bookRepository, never()).save(any(Book.class));
Resumo
Mockito é uma biblioteca para criação de objetos mock que permite simular o comportamento de dependências e isolar o código em teste. Isso simplifica os testes, tornando-os mais rápidos, mais estáveis e independentes de recursos externos.
Obrigado pelo seu feedback!