Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Lära Mockito | Testning av Backendapplikationer
Spring Boot Backend

bookMockito

I föregående kapitel gick vi igenom hur man skriver enhetstester. Dock är detta inte alltid praktiskt i vår applikation eftersom testning av en controller kräver testning av servicen, och testning av servicen kräver testning av repository.

Denna beroendekedja väcker frågan: Behöver vi testa alla moduler och deras beroenden, eller kan vi simulera deras beteende — med andra ord, mocka dem?

Mockito: Introduktion

Mockito är ett populärt bibliotek för Java som används för att skapa mock-objekt i enhetstester.

Det gör det möjligt för utvecklare att simulera beteendet hos komplexa externa beroenden (såsom databaser, tjänster eller API:er) för att isolera och testa logiken i en specifik komponent (vanligtvis en klass eller metod) utan att interagera med dessa externa system.

Detta gör enhetstestning mer förutsägbar och snabbare eftersom det inte behövs några verkliga anrop till externa resurser under testningen.

Mock-objekt i Mockito

Detta möjliggör för utvecklare att säkerställa att komponenten som testas (enheten) beter sig korrekt utan att vara beroende av verkliga beroenden såsom databaser eller tredjeparts-tjänster.

En klass kan annoteras med @Mock för att skapa dess mock-version. Denna annotation används ofta tillsammans med @InjectMocks för att automatiskt injicera beroenden i klassen som testas.

@Mock 
private MyService myServiceMock;

Detta mock-objekt kan sedan injiceras som ett beroende i ett annat objekt med hjälp av @InjectMocks-annoteringen. Denna annotering injicerar automatiskt mock-objekten i klassen som testas.

@InjectMocks
private MyController myController;

Viktiga metoder i Mockito

Mockito tillhandahåller ett brett utbud av metoder för hantering av mock-objekt, var och en med ett specifikt syfte i testprocessen. Nedan följer de mest väsentliga metoderna tillsammans med deras förklaringar.

Testning av controllers med Mockito

Testning av controllers innebär ofta användning av mocks för tjänster som anropas inom controllern. I detta sammanhang spelar Mockito en viktig roll genom att isolera controllerns logik från servicelagret.

Snabb sammanfattning av videon

Vi testar controllers med hjälp av klassen MockMvc, men vad erbjuder den och vilka är fördelarna med att använda den?

@Autowired
private MockMvc mockMvc;

Med MockMvc kan du simulera olika HTTP-förfrågningar (GET, POST, PUT, etc.), skicka parametrar, huvuden och verifiera svar, statuskoder, huvuden och svarsdata, vilket gör enhetstestning av controllers mycket enklare.

Till exempel kan du använda metoder som perform() för att utföra förfrågan, andExpect() för att verifiera de förväntade resultaten och content() för att kontrollera innehållet i svaret.

Test.java

Test.java

copy
1234
mockMvc.perform(put("/books/{id}", bookId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookRequestDTO))) .andExpect(status().isOk())

Dessutom kan du kedja assertioner för att kontrollera HTTP-status med status().isOk(), verifiera JSON-svar med jsonPath() och mer.

Test.java

Test.java

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

Dessa verktyg möjliggör omfattande testning av kontrollerns beteende utan att faktiskt göra riktiga HTTP-anrop.

Viktiga Annotations

Annoteringen @WebMvcTest(BookController.class) används för testning av BookController och laddar endast de komponenter som är nödvändiga för just den specifika controllern. Den utesluter övrig Spring-infrastruktur, vilket gör det möjligt att fokusera på att testa controllern i sig.

@WebMvcTest(BookController.class)
public class BookControllerTest 

Dessutom skapar @MockBean-annoteringen som används på bookService-fältet en mock-version av tjänsten, vilket möjliggör simulering av dess beteende. Detta hjälper till att isolera kontrollern från dess beroenden och fokusera på att testa dess logik.

@MockBean
private BookService bookService;

Tester för updateBook-metoden

Två tester har skrivits för updateBook-metoden som täcker alla möjliga fall för denna metod. I det första testet verifieras att allt var lyckat och att vår entitet uppdaterades.

BookControllerTest.java

BookControllerTest.java

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

Detta test verifierar en lyckad uppdatering av en book om den finns i databasen. updateBook()-metoden i servicen anropas med bokens Id och ett bookRequestDTO-objekt, varefter den returnerar ett bookResponseDTO-objekt som representerar den uppdaterade boken.

Kontroll av undantagshantering

Det finns även ett test där ett undantag uppstår i updateBook-metoden, och vi behöver verifiera hur controllern beter sig i den situationen.

BookControllerTest.java

BookControllerTest.java

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

Detta test kontrollerar kontrollerns beteende vid försök att uppdatera en bok som inte hittas i databasen. updateBook()-metoden i servicen anropas först med bokens Id och ett bookRequestDTO-objekt.

Istället för ett lyckat resultat kastar dock servicen ett ApiException, vilket indikerar att boken med det angivna Id inte hittades.

Servicetestning med Mockito

Vid testning av en service är det viktigt att isolera den från beroenden såsom repositoryn eller externa API:er. Mockito möjliggör att skapa mockar för dessa beroenden och definiera deras beteende i testerna.

Kort sammanfattning av videon

Vi börjar med att testa vår metod när den uppdaterar data lyckat, det vill säga när Id är giltigt. Självklart använder vi Mockito för att mocka repositoryts beteende.

BookServiceTest.java

BookServiceTest.java

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

Metoden bookRepository.findById("1") är mockad för att returnera en befintlig Book från repositoryt, och bookRepository.save(bookWithRepository) är mockad för att returnera den uppdaterade boken efter att ändringarna sparats.

Metoden updateBook() för tjänsten anropas för att uppdatera boken baserat på bookRequestDTO, och returnerar en BookResponseDTO.

assertNotNull(result) säkerställer att resultatet inte är null, vilket indikerar en lyckad uppdatering, medan assertEquals() kontrollerar att ID, name, author och price för den uppdaterade boken matchar de förväntade värdena.

 assertNotNull(result);
 assertEquals("1", result.getId());
 assertEquals("Updated Name", result.getName());
 assertEquals("Updated Author", result.getAuthor());
 assertEquals("150", result.getPrice());

Slutligen säkerställer verify() att findById() och save() i repositoryt anropades med korrekta parametrar.

verify(bookRepository).findById("1");
verify(bookRepository).save(bookWithRepository);

Kontroll av undantag i tjänsten

Detta test verifierar att tjänsten kastar ett ApiException om den Book som ska uppdateras inte hittas i databasen.

BookServiceTest.java

BookServiceTest.java

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

Inledningsvis skapas ett BookRequestDTO objekt med data för uppdateringen, och ett testbok-Id som inte finns i databasen "999" tilldelas. Metoden bookRepository.findById(idTest) mockas för att returnera Optional.empty(), vilket indikerar att det inte finns någon bok med det Id.

when(bookRepository.findById(idTest)).thenReturn(Optional.empty());

Testet använder metoden assertThrows() för att kontrollera att när anropet bookService.updateBook(idTest, bookRequestDTO) görs, kastas ett ApiException.

Därefter verifierar metoderna assertEquals() att undantagsmeddelandet matchar den förväntade texten och att felstatusen är lika med 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());

Metoden verify(bookRepository, never()).save(any(Book.class)) säkerställer att metod för att spara bok save() inte anropades, eftersom boken inte hittades och uppdateringen inte utfördes.

verify(bookRepository, never()).save(any(Book.class));

Sammanfattning

Mockito är ett bibliotek för att skapa mock-objekt som gör det möjligt att simulera beroendens beteende och isolera den testade koden. Detta förenklar testning, gör den snabbare, mer stabil och oberoende av externa resurser.

Var allt tydligt?

Hur kan vi förbättra det?

Tack för dina kommentarer!

Avsnitt 5. Kapitel 3

Fråga AI

expand

Fråga AI

ChatGPT

Fråga vad du vill eller prova någon av de föreslagna frågorna för att starta vårt samtal

Suggested prompts:

What are some common mistakes to avoid when using Mockito?

Can you explain the difference between @Mock and @MockBean?

How do I decide when to use mocks versus real objects in my tests?

Awesome!

Completion rate improved to 3.45

bookMockito

Svep för att visa menyn

I föregående kapitel gick vi igenom hur man skriver enhetstester. Dock är detta inte alltid praktiskt i vår applikation eftersom testning av en controller kräver testning av servicen, och testning av servicen kräver testning av repository.

Denna beroendekedja väcker frågan: Behöver vi testa alla moduler och deras beroenden, eller kan vi simulera deras beteende — med andra ord, mocka dem?

Mockito: Introduktion

Mockito är ett populärt bibliotek för Java som används för att skapa mock-objekt i enhetstester.

Det gör det möjligt för utvecklare att simulera beteendet hos komplexa externa beroenden (såsom databaser, tjänster eller API:er) för att isolera och testa logiken i en specifik komponent (vanligtvis en klass eller metod) utan att interagera med dessa externa system.

Detta gör enhetstestning mer förutsägbar och snabbare eftersom det inte behövs några verkliga anrop till externa resurser under testningen.

Mock-objekt i Mockito

Detta möjliggör för utvecklare att säkerställa att komponenten som testas (enheten) beter sig korrekt utan att vara beroende av verkliga beroenden såsom databaser eller tredjeparts-tjänster.

En klass kan annoteras med @Mock för att skapa dess mock-version. Denna annotation används ofta tillsammans med @InjectMocks för att automatiskt injicera beroenden i klassen som testas.

@Mock 
private MyService myServiceMock;

Detta mock-objekt kan sedan injiceras som ett beroende i ett annat objekt med hjälp av @InjectMocks-annoteringen. Denna annotering injicerar automatiskt mock-objekten i klassen som testas.

@InjectMocks
private MyController myController;

Viktiga metoder i Mockito

Mockito tillhandahåller ett brett utbud av metoder för hantering av mock-objekt, var och en med ett specifikt syfte i testprocessen. Nedan följer de mest väsentliga metoderna tillsammans med deras förklaringar.

Testning av controllers med Mockito

Testning av controllers innebär ofta användning av mocks för tjänster som anropas inom controllern. I detta sammanhang spelar Mockito en viktig roll genom att isolera controllerns logik från servicelagret.

Snabb sammanfattning av videon

Vi testar controllers med hjälp av klassen MockMvc, men vad erbjuder den och vilka är fördelarna med att använda den?

@Autowired
private MockMvc mockMvc;

Med MockMvc kan du simulera olika HTTP-förfrågningar (GET, POST, PUT, etc.), skicka parametrar, huvuden och verifiera svar, statuskoder, huvuden och svarsdata, vilket gör enhetstestning av controllers mycket enklare.

Till exempel kan du använda metoder som perform() för att utföra förfrågan, andExpect() för att verifiera de förväntade resultaten och content() för att kontrollera innehållet i svaret.

Test.java

Test.java

copy
1234
mockMvc.perform(put("/books/{id}", bookId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookRequestDTO))) .andExpect(status().isOk())

Dessutom kan du kedja assertioner för att kontrollera HTTP-status med status().isOk(), verifiera JSON-svar med jsonPath() och mer.

Test.java

Test.java

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

Dessa verktyg möjliggör omfattande testning av kontrollerns beteende utan att faktiskt göra riktiga HTTP-anrop.

Viktiga Annotations

Annoteringen @WebMvcTest(BookController.class) används för testning av BookController och laddar endast de komponenter som är nödvändiga för just den specifika controllern. Den utesluter övrig Spring-infrastruktur, vilket gör det möjligt att fokusera på att testa controllern i sig.

@WebMvcTest(BookController.class)
public class BookControllerTest 

Dessutom skapar @MockBean-annoteringen som används på bookService-fältet en mock-version av tjänsten, vilket möjliggör simulering av dess beteende. Detta hjälper till att isolera kontrollern från dess beroenden och fokusera på att testa dess logik.

@MockBean
private BookService bookService;

Tester för updateBook-metoden

Två tester har skrivits för updateBook-metoden som täcker alla möjliga fall för denna metod. I det första testet verifieras att allt var lyckat och att vår entitet uppdaterades.

BookControllerTest.java

BookControllerTest.java

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

Detta test verifierar en lyckad uppdatering av en book om den finns i databasen. updateBook()-metoden i servicen anropas med bokens Id och ett bookRequestDTO-objekt, varefter den returnerar ett bookResponseDTO-objekt som representerar den uppdaterade boken.

Kontroll av undantagshantering

Det finns även ett test där ett undantag uppstår i updateBook-metoden, och vi behöver verifiera hur controllern beter sig i den situationen.

BookControllerTest.java

BookControllerTest.java

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

Detta test kontrollerar kontrollerns beteende vid försök att uppdatera en bok som inte hittas i databasen. updateBook()-metoden i servicen anropas först med bokens Id och ett bookRequestDTO-objekt.

Istället för ett lyckat resultat kastar dock servicen ett ApiException, vilket indikerar att boken med det angivna Id inte hittades.

Servicetestning med Mockito

Vid testning av en service är det viktigt att isolera den från beroenden såsom repositoryn eller externa API:er. Mockito möjliggör att skapa mockar för dessa beroenden och definiera deras beteende i testerna.

Kort sammanfattning av videon

Vi börjar med att testa vår metod när den uppdaterar data lyckat, det vill säga när Id är giltigt. Självklart använder vi Mockito för att mocka repositoryts beteende.

BookServiceTest.java

BookServiceTest.java

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

Metoden bookRepository.findById("1") är mockad för att returnera en befintlig Book från repositoryt, och bookRepository.save(bookWithRepository) är mockad för att returnera den uppdaterade boken efter att ändringarna sparats.

Metoden updateBook() för tjänsten anropas för att uppdatera boken baserat på bookRequestDTO, och returnerar en BookResponseDTO.

assertNotNull(result) säkerställer att resultatet inte är null, vilket indikerar en lyckad uppdatering, medan assertEquals() kontrollerar att ID, name, author och price för den uppdaterade boken matchar de förväntade värdena.

 assertNotNull(result);
 assertEquals("1", result.getId());
 assertEquals("Updated Name", result.getName());
 assertEquals("Updated Author", result.getAuthor());
 assertEquals("150", result.getPrice());

Slutligen säkerställer verify() att findById() och save() i repositoryt anropades med korrekta parametrar.

verify(bookRepository).findById("1");
verify(bookRepository).save(bookWithRepository);

Kontroll av undantag i tjänsten

Detta test verifierar att tjänsten kastar ett ApiException om den Book som ska uppdateras inte hittas i databasen.

BookServiceTest.java

BookServiceTest.java

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

Inledningsvis skapas ett BookRequestDTO objekt med data för uppdateringen, och ett testbok-Id som inte finns i databasen "999" tilldelas. Metoden bookRepository.findById(idTest) mockas för att returnera Optional.empty(), vilket indikerar att det inte finns någon bok med det Id.

when(bookRepository.findById(idTest)).thenReturn(Optional.empty());

Testet använder metoden assertThrows() för att kontrollera att när anropet bookService.updateBook(idTest, bookRequestDTO) görs, kastas ett ApiException.

Därefter verifierar metoderna assertEquals() att undantagsmeddelandet matchar den förväntade texten och att felstatusen är lika med 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());

Metoden verify(bookRepository, never()).save(any(Book.class)) säkerställer att metod för att spara bok save() inte anropades, eftersom boken inte hittades och uppdateringen inte utfördes.

verify(bookRepository, never()).save(any(Book.class));

Sammanfattning

Mockito är ett bibliotek för att skapa mock-objekt som gör det möjligt att simulera beroendens beteende och isolera den testade koden. Detta förenklar testning, gör den snabbare, mer stabil och oberoende av externa resurser.

Var allt tydligt?

Hur kan vi förbättra det?

Tack för dina kommentarer!

Avsnitt 5. Kapitel 3
some-alt