Mockito
I det forrige kapitel gennemgik vi, hvordan man skriver enhedstests. Dog er dette ikke altid praktisk i vores applikation, da test af en controller kræver test af servicen, og test af servicen nødvendiggør test af repository.
Denne afhængighedskæde rejser spørgsmålet: Skal vi teste alle moduler og deres afhængigheder, eller kan vi simulere deres opførsel — med andre ord, mocke dem?
Mockito: Introduktion
Mockito er et populært bibliotek til Java, der bruges til at oprette mock-objekter i enhedstests.
Det gør det muligt for udviklere at simulere opførslen af komplekse eksterne afhængigheder (såsom databaser, services eller API'er) for at isolere og teste logikken i en specifik komponent (typisk en klasse eller metode) uden at interagere med disse eksterne systemer.
Dette gør enhedstestning mere forudsigelig og hurtigere, da der ikke er behov for reelle eksterne ressourcekald under testning.
Mock-objekter i Mockito
Dette gør det muligt for udviklere at sikre, at den testede komponent (enheden) opfører sig korrekt uden at være afhængig af reelle afhængigheder såsom databaser eller tredjeparts-tjenester.
En klasse kan annoteres med @Mock for at oprette dens mock-version. Denne annotation bruges ofte sammen med @InjectMocks for automatisk at injicere afhængigheder i den klasse, der testes.
@Mock
private MyService myServiceMock;
Dette mock-objekt kan derefter indsprøjtes som en afhængighed i et andet objekt ved hjælp af @InjectMocks annotationen. Denne annotation indsprøjter automatisk mock-objekterne i klassen, der testes.
@InjectMocks
private MyController myController;
Nøglemetoder i Mockito
Mockito tilbyder et bredt udvalg af metoder til håndtering af mock-objekter, hver med et specifikt formål i testprocessen. Nedenfor ses de mest vigtige metoder sammen med deres forklaringer.
Test af controllere med Mockito
Test af controllere involverer ofte brug af mocks for services, der kaldes inden for controlleren. I denne sammenhæng spiller Mockito en central rolle ved at isolere controllerens logik fra service-laget.
Kort opsummering af videoen
Vi tester controllere ved hjælp af MockMvc klassen, men hvad tilbyder den, og hvad er fordelene ved at bruge den?
@Autowired
private MockMvc mockMvc;
Med MockMvc kan du simulere forskellige HTTP-forespørgsler (GET, POST, PUT osv.), videregive parametre, headers og verificere svar, statuskoder, headers og svarkroppe, hvilket gør enhedstest af controllere meget nemmere.
For eksempel kan du anvende metoder som perform() til at udføre forespørgslen, andExpect() til at verificere de forventede resultater og content() til at kontrollere indholdet af svaret.
Test.java
1234mockMvc.perform(put("/books/{id}", bookId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookRequestDTO))) .andExpect(status().isOk())
Derudover kan du kæde påstande sammen for at kontrollere HTTP-status ved hjælp af status().isOk(), verificere JSON-svar-strukturer med jsonPath(), og mere.
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()));
Disse værktøjer muliggør omfattende test af controllerens adfærd uden faktisk at foretage rigtige HTTP-kald.
Nøgleannotationer
Annotationen @WebMvcTest(BookController.class) anvendes til test af BookController og indlæser kun de komponenter, der er nødvendige for den specifikke controller. Den udelader resten af Spring-infrastrukturen, hvilket muliggør fokuseret test af selve controlleren.
@WebMvcTest(BookController.class)
public class BookControllerTest
Derudover opretter @MockBean-annoteringen anvendt på bookService-feltet en mock-version af servicen, hvilket gør det muligt at simulere dens opførsel. Dette hjælper med at isolere controlleren fra dens afhængigheder og fokusere på test af dens logik.
@MockBean
private BookService bookService;
Test af updateBook-metoden
Der er skrevet to tests for updateBook-metoden, som dækker alle mulige tilfælde for denne metode. I den første test verificeres det, at alt var vellykket, og at vores entitet blev opdateret.
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); }
Denne test verificerer den vellykkede opdatering af en book, hvis den eksisterer i databasen. updateBook()-metoden for servicen kaldes med bogens Id og et bookRequestDTO-objekt, hvorefter den returnerer et bookResponseDTO-objekt, der repræsenterer den opdaterede bog.
Kontrol af undtagelseshåndtering
Der findes også en test, hvor en undtagelse opstår i updateBook-metoden, og det skal verificeres, hvordan controlleren opfører sig i denne situation.
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); }
Denne test kontrollerer controllerens adfærd ved forsøg på at opdatere en bog, der ikke findes i databasen. updateBook()-metoden i servicen kaldes først med bogens Id og et bookRequestDTO-objekt.
I stedet for et vellykket resultat kaster servicen dog en ApiException, hvilket indikerer, at bogen med det angivne Id ikke blev fundet.
Servicetest med Mockito
Ved test af en service er det vigtigt at isolere den fra afhængigheder såsom repositories eller eksterne API'er. Mockito gør det muligt at oprette mocks for disse afhængigheder og definere deres adfærd i testene.
Kort opsummering af videoen
Vi starter med at teste vores metode, når den opdaterer data succesfuldt, hvilket betyder, at Id er gyldigt. Vi anvender naturligvis Mockito til at mocke repositoryets opførsel.
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); }
Metoden bookRepository.findById("1") er mocked til at returnere en eksisterende Book fra repositoryet, og bookRepository.save(bookWithRepository) er mocked til at returnere den opdaterede bog efter ændringerne er gemt.
Metoden updateBook() servicemetode kaldes for at opdatere bogen baseret på bookRequestDTO, og returnerer en BookResponseDTO.
assertNotNull(result) sikrer, at resultatet ikke er null, hvilket indikerer en vellykket opdatering, mens assertEquals() kontrollerer, at ID, name, author og price for den opdaterede bog matcher de forventede værdier.
assertNotNull(result);
assertEquals("1", result.getId());
assertEquals("Updated Name", result.getName());
assertEquals("Updated Author", result.getAuthor());
assertEquals("150", result.getPrice());
Afslutningsvis sikrer verify(), at findById() og save() i repository blev kaldt med de korrekte parametre.
verify(bookRepository).findById("1");
verify(bookRepository).save(bookWithRepository);
Kontrol af en undtagelse i servicen
Denne test verificerer, at servicen kaster en ApiException, hvis den Book, der skal opdateres, ikke findes i databasen.
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)); }
Først oprettes et BookRequestDTO objekt med data til opdateringen, og en testbog Id, der ikke findes i databasen "999", tildeles. Metoden bookRepository.findById(idTest) mock'es til at returnere Optional.empty(), hvilket indikerer, at der ikke findes en bog med det pågældende Id.
when(bookRepository.findById(idTest)).thenReturn(Optional.empty());
Testen anvender assertThrows() metoden til at kontrollere, at der kastes en bookService.updateBook(idTest, bookRequestDTO), når ApiException kaldes.
Herefter verificerer assertEquals() metoderne, at undtagelsesbeskeden matcher den forventede tekst, og at fejlstatus er lig 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)) sikrer, at bogens gemme-metode save() ikke blev kaldt, da bogen ikke blev fundet, og opdateringen derfor ikke blev udført.
verify(bookRepository, never()).save(any(Book.class));
Resumé
Mockito er et bibliotek til oprettelse af mock-objekter, der gør det muligt at simulere afhængigheders adfærd og isolere den kode, der testes. Dette forenkler testning, gør den hurtigere, mere stabil og uafhængig af eksterne ressourcer.
Tak for dine kommentarer!
Spørg AI
Spørg AI
Spørg om hvad som helst eller prøv et af de foreslåede spørgsmål for at starte vores chat
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
Mockito
Stryg for at vise menuen
I det forrige kapitel gennemgik vi, hvordan man skriver enhedstests. Dog er dette ikke altid praktisk i vores applikation, da test af en controller kræver test af servicen, og test af servicen nødvendiggør test af repository.
Denne afhængighedskæde rejser spørgsmålet: Skal vi teste alle moduler og deres afhængigheder, eller kan vi simulere deres opførsel — med andre ord, mocke dem?
Mockito: Introduktion
Mockito er et populært bibliotek til Java, der bruges til at oprette mock-objekter i enhedstests.
Det gør det muligt for udviklere at simulere opførslen af komplekse eksterne afhængigheder (såsom databaser, services eller API'er) for at isolere og teste logikken i en specifik komponent (typisk en klasse eller metode) uden at interagere med disse eksterne systemer.
Dette gør enhedstestning mere forudsigelig og hurtigere, da der ikke er behov for reelle eksterne ressourcekald under testning.
Mock-objekter i Mockito
Dette gør det muligt for udviklere at sikre, at den testede komponent (enheden) opfører sig korrekt uden at være afhængig af reelle afhængigheder såsom databaser eller tredjeparts-tjenester.
En klasse kan annoteres med @Mock for at oprette dens mock-version. Denne annotation bruges ofte sammen med @InjectMocks for automatisk at injicere afhængigheder i den klasse, der testes.
@Mock
private MyService myServiceMock;
Dette mock-objekt kan derefter indsprøjtes som en afhængighed i et andet objekt ved hjælp af @InjectMocks annotationen. Denne annotation indsprøjter automatisk mock-objekterne i klassen, der testes.
@InjectMocks
private MyController myController;
Nøglemetoder i Mockito
Mockito tilbyder et bredt udvalg af metoder til håndtering af mock-objekter, hver med et specifikt formål i testprocessen. Nedenfor ses de mest vigtige metoder sammen med deres forklaringer.
Test af controllere med Mockito
Test af controllere involverer ofte brug af mocks for services, der kaldes inden for controlleren. I denne sammenhæng spiller Mockito en central rolle ved at isolere controllerens logik fra service-laget.
Kort opsummering af videoen
Vi tester controllere ved hjælp af MockMvc klassen, men hvad tilbyder den, og hvad er fordelene ved at bruge den?
@Autowired
private MockMvc mockMvc;
Med MockMvc kan du simulere forskellige HTTP-forespørgsler (GET, POST, PUT osv.), videregive parametre, headers og verificere svar, statuskoder, headers og svarkroppe, hvilket gør enhedstest af controllere meget nemmere.
For eksempel kan du anvende metoder som perform() til at udføre forespørgslen, andExpect() til at verificere de forventede resultater og content() til at kontrollere indholdet af svaret.
Test.java
1234mockMvc.perform(put("/books/{id}", bookId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookRequestDTO))) .andExpect(status().isOk())
Derudover kan du kæde påstande sammen for at kontrollere HTTP-status ved hjælp af status().isOk(), verificere JSON-svar-strukturer med jsonPath(), og mere.
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()));
Disse værktøjer muliggør omfattende test af controllerens adfærd uden faktisk at foretage rigtige HTTP-kald.
Nøgleannotationer
Annotationen @WebMvcTest(BookController.class) anvendes til test af BookController og indlæser kun de komponenter, der er nødvendige for den specifikke controller. Den udelader resten af Spring-infrastrukturen, hvilket muliggør fokuseret test af selve controlleren.
@WebMvcTest(BookController.class)
public class BookControllerTest
Derudover opretter @MockBean-annoteringen anvendt på bookService-feltet en mock-version af servicen, hvilket gør det muligt at simulere dens opførsel. Dette hjælper med at isolere controlleren fra dens afhængigheder og fokusere på test af dens logik.
@MockBean
private BookService bookService;
Test af updateBook-metoden
Der er skrevet to tests for updateBook-metoden, som dækker alle mulige tilfælde for denne metode. I den første test verificeres det, at alt var vellykket, og at vores entitet blev opdateret.
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); }
Denne test verificerer den vellykkede opdatering af en book, hvis den eksisterer i databasen. updateBook()-metoden for servicen kaldes med bogens Id og et bookRequestDTO-objekt, hvorefter den returnerer et bookResponseDTO-objekt, der repræsenterer den opdaterede bog.
Kontrol af undtagelseshåndtering
Der findes også en test, hvor en undtagelse opstår i updateBook-metoden, og det skal verificeres, hvordan controlleren opfører sig i denne situation.
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); }
Denne test kontrollerer controllerens adfærd ved forsøg på at opdatere en bog, der ikke findes i databasen. updateBook()-metoden i servicen kaldes først med bogens Id og et bookRequestDTO-objekt.
I stedet for et vellykket resultat kaster servicen dog en ApiException, hvilket indikerer, at bogen med det angivne Id ikke blev fundet.
Servicetest med Mockito
Ved test af en service er det vigtigt at isolere den fra afhængigheder såsom repositories eller eksterne API'er. Mockito gør det muligt at oprette mocks for disse afhængigheder og definere deres adfærd i testene.
Kort opsummering af videoen
Vi starter med at teste vores metode, når den opdaterer data succesfuldt, hvilket betyder, at Id er gyldigt. Vi anvender naturligvis Mockito til at mocke repositoryets opførsel.
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); }
Metoden bookRepository.findById("1") er mocked til at returnere en eksisterende Book fra repositoryet, og bookRepository.save(bookWithRepository) er mocked til at returnere den opdaterede bog efter ændringerne er gemt.
Metoden updateBook() servicemetode kaldes for at opdatere bogen baseret på bookRequestDTO, og returnerer en BookResponseDTO.
assertNotNull(result) sikrer, at resultatet ikke er null, hvilket indikerer en vellykket opdatering, mens assertEquals() kontrollerer, at ID, name, author og price for den opdaterede bog matcher de forventede værdier.
assertNotNull(result);
assertEquals("1", result.getId());
assertEquals("Updated Name", result.getName());
assertEquals("Updated Author", result.getAuthor());
assertEquals("150", result.getPrice());
Afslutningsvis sikrer verify(), at findById() og save() i repository blev kaldt med de korrekte parametre.
verify(bookRepository).findById("1");
verify(bookRepository).save(bookWithRepository);
Kontrol af en undtagelse i servicen
Denne test verificerer, at servicen kaster en ApiException, hvis den Book, der skal opdateres, ikke findes i databasen.
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)); }
Først oprettes et BookRequestDTO objekt med data til opdateringen, og en testbog Id, der ikke findes i databasen "999", tildeles. Metoden bookRepository.findById(idTest) mock'es til at returnere Optional.empty(), hvilket indikerer, at der ikke findes en bog med det pågældende Id.
when(bookRepository.findById(idTest)).thenReturn(Optional.empty());
Testen anvender assertThrows() metoden til at kontrollere, at der kastes en bookService.updateBook(idTest, bookRequestDTO), når ApiException kaldes.
Herefter verificerer assertEquals() metoderne, at undtagelsesbeskeden matcher den forventede tekst, og at fejlstatus er lig 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)) sikrer, at bogens gemme-metode save() ikke blev kaldt, da bogen ikke blev fundet, og opdateringen derfor ikke blev udført.
verify(bookRepository, never()).save(any(Book.class));
Resumé
Mockito er et bibliotek til oprettelse af mock-objekter, der gør det muligt at simulere afhængigheders adfærd og isolere den kode, der testes. Dette forenkler testning, gør den hurtigere, mere stabil og uafhængig af eksterne ressourcer.
Tak for dine kommentarer!