Kursinhalt
Spring Boot Backend
Spring Boot Backend
Mockito
Im vorherigen Kapitel haben wir uns daran erinnert, wie man Unit-Tests schreibt. In unserer Anwendung ist dies jedoch nicht immer praktisch, da das Testen eines Controllers das Testen des Service erfordert und das Testen des Service das Testen des Repository notwendig macht.
Diese Kette von Abhängigkeiten wirft die Frage auf: Müssen wir alle Module und ihre Abhängigkeiten testen, oder können wir ihr Verhalten simulieren — mit anderen Worten, sie mocken?
Mockito: Einführung
Mockito ist eine beliebte Bibliothek für Java, die verwendet wird, um Mock-Objekte in Unit-Tests zu erstellen.
Es ermöglicht Entwicklern, das Verhalten komplexer externer Abhängigkeiten (wie Datenbanken, Dienste oder APIs) zu simulieren, um die Logik einer bestimmten Komponente (normalerweise eine Klasse oder Methode) zu isolieren und zu testen, ohne mit diesen externen Systemen zu interagieren.
Dies macht Unit-Tests vorhersehbarer und schneller, da während des Testens keine echten externen Ressourcen aufgerufen werden müssen.
Mock-Objekte in Mockito
Dies ermöglicht Entwicklern, sicherzustellen, dass die zu testende Komponente (die Einheit) korrekt funktioniert, ohne sich auf echte Abhängigkeiten wie Datenbanken oder Dienste von Drittanbietern zu verlassen.
Eine Klasse kann mit @Mock
annotiert werden, um ihre Mock-Version zu erstellen. Diese Annotation wird oft zusammen mit @InjectMocks
verwendet, um Abhängigkeiten automatisch in die zu testende Klasse zu injizieren.
Dieses Mock-Objekt kann dann als Abhängigkeit in ein anderes Objekt mit der @InjectMocks
Annotation injiziert werden. Diese Annotation injiziert automatisch die Mock-Objekte in die zu testende Klasse.
Schlüsselmethoden in Mockito
Mockito bietet eine breite Palette von Methoden zur Verwaltung von Mock-Objekten, wobei jede einem bestimmten Zweck im Testprozess dient. Im Folgenden sind die wichtigsten Methoden zusammen mit ihren Erklärungen aufgeführt.
Testen von Controllern mit Mockito
Das Testen von Controllern beinhaltet oft die Verwendung von Mocks für Services, die innerhalb des Controllers aufgerufen werden. In diesem Kontext spielt Mockito eine Schlüsselrolle, um die Logik des Controllers von der Service-Schicht zu isolieren.
Kurze Zusammenfassung des Videos
Wir testen Controller mit der Klasse MockMvc
, aber was bietet sie und welche Vorteile hat die Verwendung?
Mit MockMvc
können Sie verschiedene HTTP-Anfragen (GET, POST, PUT, etc.) simulieren, Parameter und Header übergeben und Antworten, Statuscodes, Header und Antwortinhalte überprüfen, was das Unit-Testing von Controllern erheblich erleichtert.
Zum Beispiel können Sie Methoden wie perform()
verwenden, um die Anfrage auszuführen, andExpect()
, um die erwarteten Ergebnisse zu überprüfen, und content()
, um den Inhalt der Antwort zu prüfen.
Test
mockMvc.perform(put("/books/{id}", bookId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookRequestDTO))) .andExpect(status().isOk())
Zusätzlich können Sie Assertions verketten, um den HTTP-Status mit status().isOk()
zu überprüfen, JSON-Antwortstrukturen mit jsonPath()
zu verifizieren und mehr.
Test
.andExpect(jsonPath("$.id").value(bookResponseDTO.getId())) .andExpect(jsonPath("$.name").value(bookResponseDTO.getName())) .andExpect(jsonPath("$.author").value(bookResponseDTO.getAuthor())) .andExpect(jsonPath("$.price").value(bookResponseDTO.getPrice()));
Diese Werkzeuge ermöglichen eine umfassende Prüfung des Verhaltens des Controllers, ohne tatsächlich echte HTTP-Anfragen zu stellen.
Wichtige Anmerkungen
Die @WebMvcTest(BookController.class)
Annotation wird verwendet, um den BookController
zu testen, wobei nur die Komponenten geladen werden, die für diesen spezifischen Controller notwendig sind. Sie schließt den Rest der Spring-Infrastruktur aus, sodass Sie sich auf das Testen des Controllers selbst konzentrieren können.
Zusätzlich erstellt die @MockBean
Annotation, die auf das bookService
Feld angewendet wird, eine Mock-Version des Dienstes, die es Ihnen ermöglicht, sein Verhalten zu simulieren. Dies hilft, den Controller von seinen Abhängigkeiten zu isolieren und sich auf das Testen seiner Logik zu konzentrieren.
Tests für die updateBook-Methode
Wir haben zwei Tests für die updateBook
-Methode geschrieben, die alle möglichen Fälle für diese Methode abdecken. Im ersten Test überprüfen wir, dass alles erfolgreich war und dass unsere Entität aktualisiert wurde.
BookControllerTest
@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); }
Dieser Test überprüft die erfolgreiche Aktualisierung eines book
, wenn es in der Datenbank existiert. Die updateBook()
-Methode des Service wird mit der Id
des Buches und einem bookRequestDTO
-Objekt aufgerufen, wonach sie ein bookResponseDTO
-Objekt zurückgibt, das das aktualisierte Buch darstellt.
Überprüfung der Ausnahmebehandlung
Wir haben auch einen Test, bei dem eine Ausnahme in der updateBook
-Methode auftritt, und wir müssen überprüfen, wie sich der Controller in dieser Situation verhält.
BookControllerTest
@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); }
Dieser Test überprüft das Verhalten des Controllers, wenn versucht wird, ein Buch zu aktualisieren, das in der Datenbank nicht gefunden wird. Die updateBook()
-Methode des Service wird zuerst mit der Id
des Buches und einem bookRequestDTO
-Objekt aufgerufen.
Anstatt jedoch eines erfolgreichen Ergebnisses wirft der Service eine ApiException
, die anzeigt, dass das Buch mit der angegebenen Id
nicht gefunden wurde.
Servicetests mit Mockito
Beim Testen eines Service ist es wichtig, ihn von Abhängigkeiten wie Repositories oder externen APIs zu isolieren. Mockito
ermöglicht es Ihnen, Mocks für diese Abhängigkeiten zu erstellen und deren Verhalten in den Tests zu definieren.
Kurze Zusammenfassung des Videos
Beginnen wir mit dem Testen unserer Methode, wenn sie die Daten erfolgreich aktualisiert, was bedeutet, dass die Id
gültig ist. Natürlich verwenden wir Mockito
, um das Verhalten des Repositories zu mocken.
BookServiceTest
@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); }
Die Methode bookRepository.findById("1")
wird gemockt, um ein vorhandenes Book
aus dem Repository zurückzugeben, und bookRepository.save(bookWithRepository)
wird gemockt, um das aktualisierte Buch nach dem Speichern der Änderungen zurückzugeben.
Die updateBook()
Servicemethode wird aufgerufen, um das Buch basierend auf dem bookRequestDTO
zu aktualisieren und gibt ein BookResponseDTO
zurück.
assertNotNull(result)
stellt sicher, dass das Ergebnis nicht null
ist, was auf eine erfolgreiche Aktualisierung hinweist, während assertEquals()
überprüft, dass die ID
, der name
, der author
und der price
des aktualisierten Buches den erwarteten Werten entsprechen.
Schließlich stellt verify()
sicher, dass findById()
und save()
im Repository mit den korrekten Parametern aufgerufen wurden.
Überprüfung einer Ausnahme im Service
Dieser Test überprüft, dass der Service eine ApiException
auslöst, wenn das zu aktualisierende Book
nicht in der Datenbank gefunden wird.
BookServiceTest
@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)); }
Zunächst wird ein BookRequestDTO
Objekt mit den Daten für die Aktualisierung erstellt, und eine Testbuch-Id
, die nicht in der Datenbank existiert, "999"
, wird zugewiesen. Die Methode bookRepository.findById(idTest)
wird gemockt, um Optional.empty()
zurückzugeben, was anzeigt, dass es kein Buch mit dieser Id
gibt.
Der Test verwendet die assertThrows()
Methode, um zu überprüfen, dass beim Aufruf von bookService.updateBook(idTest, bookRequestDTO)
eine ApiException
ausgelöst wird.
Anschließend überprüfen die assertEquals()
Methoden, dass die Ausnahme-Nachricht dem erwarteten Text entspricht und dass der Fehlerstatus gleich HttpStatus.NOT_FOUND
ist.
Die Methode verify(bookRepository, never()).save(any(Book.class))
stellt sicher, dass die Buchspeicher-Methode save()
nicht aufgerufen wurde, da das Buch nicht gefunden wurde und die Aktualisierung nicht durchgeführt wurde.
Zusammenfassung
Mockito
ist eine Bibliothek zur Erstellung von Mock-Objekten, die es Ihnen ermöglicht, das Verhalten von Abhängigkeiten zu simulieren und den Code unter Test zu isolieren. Dies vereinfacht das Testen, macht es schneller, stabiler und unabhängig von externen Ressourcen.
Danke für Ihr Feedback!