Mockito
In het vorige hoofdstuk hebben we besproken hoe je unit tests schrijft. In onze applicatie is dit echter niet altijd handig, omdat het testen van een controller vereist dat je ook de service test, en het testen van de service vereist dat je de repository test.
Deze keten van afhankelijkheden roept de vraag op: Moeten we alle modules en hun afhankelijkheden testen, of kunnen we hun gedrag simuleren — met andere woorden, mocken?
Mockito: Introductie
Mockito is een populaire bibliotheek voor Java die wordt gebruikt om mock-objecten te maken in unit tests.
Het stelt ontwikkelaars in staat om het gedrag van complexe externe afhankelijkheden (zoals databases, services of API's) te simuleren om de logica van een specifiek component (meestal een klasse of methode) te isoleren en testen zonder te interageren met deze externe systemen.
Dit maakt unit testing meer voorspelbaar en sneller, omdat er geen echte oproepen naar externe bronnen nodig zijn tijdens het testen.
Mock-objecten in Mockito
Hiermee kunnen ontwikkelaars garanderen dat de te testen component (de unit) correct functioneert zonder afhankelijk te zijn van echte afhankelijkheden zoals databases of externe services.
Een klasse kan worden aangeduid met @Mock om een mock-versie ervan te maken. Deze annotatie wordt vaak gebruikt in combinatie met @InjectMocks om afhankelijkheden automatisch te injecteren in de te testen klasse.
@Mock
private MyService myServiceMock;
Dit mock-object kan vervolgens als een afhankelijkheid worden geïnjecteerd in een ander object met behulp van de @InjectMocks annotatie. Deze annotatie injecteert automatisch de mock-objecten in de te testen klasse.
@InjectMocks
private MyController myController;
Belangrijke Methoden in Mockito
Mockito biedt een breed scala aan methoden voor het beheren van mock-objecten, waarvan elk een specifiek doel dient in het testproces. Hieronder staan de meest belangrijke methoden met hun uitleg.
Controllers testen met Mockito
Het testen van controllers omvat vaak het gebruiken van mocks voor services die binnen de controller worden aangeroepen. In deze context speelt Mockito een belangrijke rol bij het isoleren van de logica van de controller van de servicelaag.
Korte samenvatting van de video
We testen controllers met de MockMvc klasse, maar wat biedt het en wat zijn de voordelen van het gebruik ervan?
@Autowired
private MockMvc mockMvc;
Met MockMvc kunnen verschillende HTTP-verzoeken (GET, POST, PUT, enzovoort) gesimuleerd worden, waarbij parameters, headers en antwoorden, statuscodes, headers en response bodies gecontroleerd kunnen worden. Dit maakt het unittesting van controllers aanzienlijk eenvoudiger.
Bijvoorbeeld, methoden zoals perform() kunnen gebruikt worden om het verzoek uit te voeren, andExpect() om de verwachte resultaten te controleren, en content() om de inhoud van het antwoord te verifiëren.
Test.java
1234mockMvc.perform(put("/books/{id}", bookId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookRequestDTO))) .andExpect(status().isOk())
Bovendien kun je asserties koppelen om de HTTP-status te controleren met status().isOk(), de JSON-responsstructuren verifiëren met jsonPath(), en meer.
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()));
Deze tools maken uitgebreide tests van het gedrag van de controller mogelijk zonder daadwerkelijk echte HTTP-aanroepen uit te voeren.
Belangrijke annotaties
De @WebMvcTest(BookController.class) annotatie wordt gebruikt voor het testen van de BookController, waarbij alleen de componenten worden geladen die nodig zijn voor die specifieke controller. Het sluit de rest van de Spring-infrastructuur uit, zodat de focus ligt op het testen van de controller zelf.
@WebMvcTest(BookController.class)
public class BookControllerTest
Bovendien zorgt de @MockBean annotatie toegepast op het bookService veld voor een mockversie van de service, waardoor het mogelijk is het gedrag ervan te simuleren. Dit helpt om de controller te isoleren van zijn afhankelijkheden en zich te richten op het testen van de logica.
@MockBean
private BookService bookService;
Tests voor de updateBook-methode
Er zijn twee tests geschreven voor de updateBook methode die alle mogelijke gevallen voor deze methode dekken. In de eerste test wordt gecontroleerd of alles succesvol is verlopen en of onze entiteit is bijgewerkt.
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); }
Deze test verifieert de succesvolle bijwerking van een book indien deze aanwezig is in de database. De updateBook() methode van de service wordt aangeroepen met het Id van het boek en een bookRequestDTO object, waarna het een bookResponseDTO object retourneert dat het bijgewerkte boek weergeeft.
Controle op Exception Handling
Er is ook een test waarbij een exceptie optreedt in de updateBook methode, en we moeten controleren hoe de controller zich in die situatie gedraagt.
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); }
Deze test controleert het gedrag van de controller bij het bijwerken van een boek dat niet gevonden wordt in de database. De updateBook() methode van de service wordt eerst aangeroepen met het Id van het boek en een bookRequestDTO object.
In plaats van een succesvol resultaat gooit de service echter een ApiException, wat aangeeft dat het boek met het opgegeven Id niet gevonden is.
Service testen met Mockito
Bij het testen van een service is het belangrijk om deze te isoleren van afhankelijkheden zoals repositories of externe API's. Met Mockito kun je mocks maken voor deze afhankelijkheden en hun gedrag definiëren in de tests.
Korte samenvatting van de video
We beginnen met het testen van onze methode wanneer deze de gegevens succesvol bijwerkt, wat betekent dat de Id geldig is. Uiteraard gebruiken we Mockito om het gedrag van de repository te mocken.
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); }
De methode bookRepository.findById("1") wordt gemockt om een bestaand Book uit de repository te retourneren, en bookRepository.save(bookWithRepository) wordt gemockt om het bijgewerkte boek terug te geven na het opslaan van de wijzigingen.
De updateBook() servicemethode wordt aangeroepen om het boek bij te werken op basis van de bookRequestDTO, en retourneert een BookResponseDTO.
assertNotNull(result) zorgt ervoor dat het resultaat niet null is, wat wijst op een geslaagde update, terwijl assertEquals() controleert of de ID, name, author en price van het bijgewerkte boek overeenkomen met de verwachte waarden.
assertNotNull(result);
assertEquals("1", result.getId());
assertEquals("Updated Name", result.getName());
assertEquals("Updated Author", result.getAuthor());
assertEquals("150", result.getPrice());
Tot slot zorgt verify() ervoor dat findById() en save() in de repository zijn aangeroepen met de juiste parameters.
verify(bookRepository).findById("1");
verify(bookRepository).save(bookWithRepository);
Controleren van een Exceptie in de Service
Deze test verifieert dat de service een ApiException gooit als het te updaten Book niet gevonden wordt in de database.
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)); }
Aanvankelijk wordt een BookRequestDTO object aangemaakt met de gegevens voor de update, en een testboek Id die niet bestaat in de database "999" wordt toegekend. De methode bookRepository.findById(idTest) wordt gemockt om Optional.empty() terug te geven, wat aangeeft dat er geen boek met dat Id is.
when(bookRepository.findById(idTest)).thenReturn(Optional.empty());
De test gebruikt de assertThrows() methode om te controleren dat bij het aanroepen van bookService.updateBook(idTest, bookRequestDTO), een ApiException wordt gegenereerd.
Daarna verifiëren de assertEquals() methoden dat het exceptionbericht overeenkomt met de verwachte tekst en dat de foutstatus gelijk is aan 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());
De methode verify(bookRepository, never()).save(any(Book.class)) garandeert dat de boekopslag methode save() niet is aangeroepen, omdat het boek niet is gevonden en de update niet is uitgevoerd.
verify(bookRepository, never()).save(any(Book.class));
Samenvatting
Mockito is een bibliotheek voor het maken van mockobjecten waarmee het gedrag van afhankelijkheden kan worden gesimuleerd en de te testen code kan worden geïsoleerd. Dit vereenvoudigt het testen, maakt het sneller, stabieler en onafhankelijk van externe bronnen.
Bedankt voor je feedback!
Vraag AI
Vraag AI
Vraag wat u wilt of probeer een van de voorgestelde vragen om onze chat te starten.
Awesome!
Completion rate improved to 3.45
Mockito
Veeg om het menu te tonen
In het vorige hoofdstuk hebben we besproken hoe je unit tests schrijft. In onze applicatie is dit echter niet altijd handig, omdat het testen van een controller vereist dat je ook de service test, en het testen van de service vereist dat je de repository test.
Deze keten van afhankelijkheden roept de vraag op: Moeten we alle modules en hun afhankelijkheden testen, of kunnen we hun gedrag simuleren — met andere woorden, mocken?
Mockito: Introductie
Mockito is een populaire bibliotheek voor Java die wordt gebruikt om mock-objecten te maken in unit tests.
Het stelt ontwikkelaars in staat om het gedrag van complexe externe afhankelijkheden (zoals databases, services of API's) te simuleren om de logica van een specifiek component (meestal een klasse of methode) te isoleren en testen zonder te interageren met deze externe systemen.
Dit maakt unit testing meer voorspelbaar en sneller, omdat er geen echte oproepen naar externe bronnen nodig zijn tijdens het testen.
Mock-objecten in Mockito
Hiermee kunnen ontwikkelaars garanderen dat de te testen component (de unit) correct functioneert zonder afhankelijk te zijn van echte afhankelijkheden zoals databases of externe services.
Een klasse kan worden aangeduid met @Mock om een mock-versie ervan te maken. Deze annotatie wordt vaak gebruikt in combinatie met @InjectMocks om afhankelijkheden automatisch te injecteren in de te testen klasse.
@Mock
private MyService myServiceMock;
Dit mock-object kan vervolgens als een afhankelijkheid worden geïnjecteerd in een ander object met behulp van de @InjectMocks annotatie. Deze annotatie injecteert automatisch de mock-objecten in de te testen klasse.
@InjectMocks
private MyController myController;
Belangrijke Methoden in Mockito
Mockito biedt een breed scala aan methoden voor het beheren van mock-objecten, waarvan elk een specifiek doel dient in het testproces. Hieronder staan de meest belangrijke methoden met hun uitleg.
Controllers testen met Mockito
Het testen van controllers omvat vaak het gebruiken van mocks voor services die binnen de controller worden aangeroepen. In deze context speelt Mockito een belangrijke rol bij het isoleren van de logica van de controller van de servicelaag.
Korte samenvatting van de video
We testen controllers met de MockMvc klasse, maar wat biedt het en wat zijn de voordelen van het gebruik ervan?
@Autowired
private MockMvc mockMvc;
Met MockMvc kunnen verschillende HTTP-verzoeken (GET, POST, PUT, enzovoort) gesimuleerd worden, waarbij parameters, headers en antwoorden, statuscodes, headers en response bodies gecontroleerd kunnen worden. Dit maakt het unittesting van controllers aanzienlijk eenvoudiger.
Bijvoorbeeld, methoden zoals perform() kunnen gebruikt worden om het verzoek uit te voeren, andExpect() om de verwachte resultaten te controleren, en content() om de inhoud van het antwoord te verifiëren.
Test.java
1234mockMvc.perform(put("/books/{id}", bookId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookRequestDTO))) .andExpect(status().isOk())
Bovendien kun je asserties koppelen om de HTTP-status te controleren met status().isOk(), de JSON-responsstructuren verifiëren met jsonPath(), en meer.
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()));
Deze tools maken uitgebreide tests van het gedrag van de controller mogelijk zonder daadwerkelijk echte HTTP-aanroepen uit te voeren.
Belangrijke annotaties
De @WebMvcTest(BookController.class) annotatie wordt gebruikt voor het testen van de BookController, waarbij alleen de componenten worden geladen die nodig zijn voor die specifieke controller. Het sluit de rest van de Spring-infrastructuur uit, zodat de focus ligt op het testen van de controller zelf.
@WebMvcTest(BookController.class)
public class BookControllerTest
Bovendien zorgt de @MockBean annotatie toegepast op het bookService veld voor een mockversie van de service, waardoor het mogelijk is het gedrag ervan te simuleren. Dit helpt om de controller te isoleren van zijn afhankelijkheden en zich te richten op het testen van de logica.
@MockBean
private BookService bookService;
Tests voor de updateBook-methode
Er zijn twee tests geschreven voor de updateBook methode die alle mogelijke gevallen voor deze methode dekken. In de eerste test wordt gecontroleerd of alles succesvol is verlopen en of onze entiteit is bijgewerkt.
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); }
Deze test verifieert de succesvolle bijwerking van een book indien deze aanwezig is in de database. De updateBook() methode van de service wordt aangeroepen met het Id van het boek en een bookRequestDTO object, waarna het een bookResponseDTO object retourneert dat het bijgewerkte boek weergeeft.
Controle op Exception Handling
Er is ook een test waarbij een exceptie optreedt in de updateBook methode, en we moeten controleren hoe de controller zich in die situatie gedraagt.
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); }
Deze test controleert het gedrag van de controller bij het bijwerken van een boek dat niet gevonden wordt in de database. De updateBook() methode van de service wordt eerst aangeroepen met het Id van het boek en een bookRequestDTO object.
In plaats van een succesvol resultaat gooit de service echter een ApiException, wat aangeeft dat het boek met het opgegeven Id niet gevonden is.
Service testen met Mockito
Bij het testen van een service is het belangrijk om deze te isoleren van afhankelijkheden zoals repositories of externe API's. Met Mockito kun je mocks maken voor deze afhankelijkheden en hun gedrag definiëren in de tests.
Korte samenvatting van de video
We beginnen met het testen van onze methode wanneer deze de gegevens succesvol bijwerkt, wat betekent dat de Id geldig is. Uiteraard gebruiken we Mockito om het gedrag van de repository te mocken.
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); }
De methode bookRepository.findById("1") wordt gemockt om een bestaand Book uit de repository te retourneren, en bookRepository.save(bookWithRepository) wordt gemockt om het bijgewerkte boek terug te geven na het opslaan van de wijzigingen.
De updateBook() servicemethode wordt aangeroepen om het boek bij te werken op basis van de bookRequestDTO, en retourneert een BookResponseDTO.
assertNotNull(result) zorgt ervoor dat het resultaat niet null is, wat wijst op een geslaagde update, terwijl assertEquals() controleert of de ID, name, author en price van het bijgewerkte boek overeenkomen met de verwachte waarden.
assertNotNull(result);
assertEquals("1", result.getId());
assertEquals("Updated Name", result.getName());
assertEquals("Updated Author", result.getAuthor());
assertEquals("150", result.getPrice());
Tot slot zorgt verify() ervoor dat findById() en save() in de repository zijn aangeroepen met de juiste parameters.
verify(bookRepository).findById("1");
verify(bookRepository).save(bookWithRepository);
Controleren van een Exceptie in de Service
Deze test verifieert dat de service een ApiException gooit als het te updaten Book niet gevonden wordt in de database.
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)); }
Aanvankelijk wordt een BookRequestDTO object aangemaakt met de gegevens voor de update, en een testboek Id die niet bestaat in de database "999" wordt toegekend. De methode bookRepository.findById(idTest) wordt gemockt om Optional.empty() terug te geven, wat aangeeft dat er geen boek met dat Id is.
when(bookRepository.findById(idTest)).thenReturn(Optional.empty());
De test gebruikt de assertThrows() methode om te controleren dat bij het aanroepen van bookService.updateBook(idTest, bookRequestDTO), een ApiException wordt gegenereerd.
Daarna verifiëren de assertEquals() methoden dat het exceptionbericht overeenkomt met de verwachte tekst en dat de foutstatus gelijk is aan 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());
De methode verify(bookRepository, never()).save(any(Book.class)) garandeert dat de boekopslag methode save() niet is aangeroepen, omdat het boek niet is gevonden en de update niet is uitgevoerd.
verify(bookRepository, never()).save(any(Book.class));
Samenvatting
Mockito is een bibliotheek voor het maken van mockobjecten waarmee het gedrag van afhankelijkheden kan worden gesimuleerd en de te testen code kan worden geïsoleerd. Dit vereenvoudigt het testen, maakt het sneller, stabieler en onafhankelijk van externe bronnen.
Bedankt voor je feedback!