Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Lære Mockito | Testing av Backend-Applikasjoner
Spring Boot Backend

bookMockito

I forrige kapittel repeterte vi hvordan man skriver enhetstester. Likevel er dette ikke alltid hensiktsmessig i vår applikasjon, fordi testing av en controller krever testing av service, og testing av service forutsetter testing av repository.

Denne avhengighetskjeden reiser spørsmålet: Må vi teste alle moduler og deres avhengigheter, eller kan vi simulere deres oppførsel — med andre ord, mocke dem?

Mockito: Introduksjon

Mockito er et populært bibliotek for Java som brukes til å lage mock-objekter i enhetstester.

Det lar utviklere simulere oppførselen til komplekse eksterne avhengigheter (som databaser, tjenester eller API-er) for å isolere og teste logikken til en spesifikk komponent (vanligvis en klasse eller metode) uten å interagere med disse eksterne systemene.

Dette gjør enhetstesting mer forutsigbar og raskere siden det ikke er behov for reelle eksterne ressurskall under testing.

Mock-objekter i Mockito

Dette gjør det mulig for utviklere å sikre at komponenten som testes (enheten) oppfører seg korrekt uten å være avhengig av reelle avhengigheter som databaser eller tredjeparts tjenester.

En klasse kan annoteres med @Mock for å opprette en mock-versjon. Denne annotasjonen brukes ofte sammen med @InjectMocks for å automatisk injisere avhengigheter i klassen som testes.

@Mock 
private MyService myServiceMock;

Dette mock-objektet kan deretter injiseres som en avhengighet i et annet objekt ved å bruke @InjectMocks-annotasjonen. Denne annotasjonen injiserer automatisk mock-objektene i klassen som testes.

@InjectMocks
private MyController myController;

Viktige metoder i Mockito

Mockito tilbyr et bredt utvalg av metoder for å håndtere mock-objekter, hver med et spesifikt formål i testprosessen. Nedenfor er de mest viktige metodene sammen med deres forklaringer.

Testing av kontrollere med Mockito

Testing av kontrollere innebærer ofte bruk av mocks for tjenester som kalles innenfor kontrolleren. I denne sammenhengen spiller Mockito en viktig rolle ved å isolere kontrollerens logikk fra tjenestelaget.

Kort oppsummering av videoen

Vi tester kontrollere ved å bruke MockMvc klassen, men hva tilbyr den og hva er fordelene ved å bruke den?

@Autowired
private MockMvc mockMvc;

Med MockMvc kan du simulere ulike HTTP-forespørsler (GET, POST, PUT, osv.), sende parametere, headere, og verifisere responser, statuskoder, headere og responsinnhold, noe som gjør enhetstesting av kontrollere mye enklere.

For eksempel kan du bruke metoder som perform() for å utføre forespørselen, andExpect() for å verifisere de forventede resultatene, og content() for å sjekke innholdet i responsen.

Test.java

Test.java

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

I tillegg kan du kjedekoble påstander for å kontrollere HTTP-status med status().isOk(), verifisere JSON-respons-strukturer med jsonPath(), og 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()));

Disse verktøyene muliggjør omfattende testing av controllerens oppførsel uten å faktisk utføre ekte HTTP-kall.

Viktige annotasjoner

Annotasjonen @WebMvcTest(BookController.class) brukes for testing av BookController, og laster kun de komponentene som er nødvendige for den spesifikke controlleren. Den utelater resten av Spring-infrastrukturen, slik at du kan fokusere på å teste controlleren i seg selv.

@WebMvcTest(BookController.class)
public class BookControllerTest 

I tillegg oppretter @MockBean-annotasjonen brukt på bookService-feltet en mock-versjon av tjenesten, slik at du kan simulere dens oppførsel. Dette bidrar til å isolere controlleren fra dens avhengigheter og gir mulighet til å fokusere på testing av logikken.

@MockBean
private BookService bookService;

Tester for updateBook-metoden

Vi har skrevet to tester for updateBook-metoden som dekker alle mulige tilfeller for denne metoden. I den første testen verifiserer vi at alt var vellykket og at vår entitet ble oppdatert.

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

Denne testen verifiserer en vellykket oppdatering av en book dersom den finnes i databasen. updateBook()-metoden til tjenesten kalles med bokens Id og et bookRequestDTO-objekt, hvorpå den returnerer et bookResponseDTO-objekt som representerer den oppdaterte boken.

Sjekk av unntakshåndtering

Det finnes også en test hvor et unntak oppstår i updateBook-metoden, og det er nødvendig å verifisere hvordan kontrolleren oppfører seg i en slik situasjon.

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

Denne testen kontrollerer kontrollerens oppførsel ved forsøk på å oppdatere en bok som ikke finnes i databasen. updateBook()-metoden til tjenesten kalles først med bokens Id og et bookRequestDTO-objekt.

I stedet for et vellykket resultat, kaster tjenesten en ApiException, som indikerer at boken med den gitte Id ikke ble funnet.

Tjenestetesting med Mockito

Ved testing av en tjeneste er det viktig å isolere den fra avhengigheter som repositories eller eksterne API-er. Mockito gjør det mulig å lage mock-objekter for disse avhengighetene og definere deres oppførsel i testene.

Kort oppsummering av videoen

Vi starter med testing av metoden når den oppdaterer data vellykket, det vil si at Id er gyldig. Vi bruker selvfølgelig Mockito for å mocke repositoryets oppførsel.

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") er mocked til å returnere en eksisterende Book fra repositoryet, og bookRepository.save(bookWithRepository) er mocked til å returnere den oppdaterte boken etter at endringene er lagret.

Metoden updateBook() for tjenesten kalles for å oppdatere boken basert på bookRequestDTO, og returnerer en BookResponseDTO.

assertNotNull(result) sikrer at resultatet ikke er null, noe som indikerer en vellykket oppdatering, mens assertEquals() kontrollerer at ID, name, author og price for den oppdaterte boken samsvarer med forventede verdier.

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

Til slutt sørger verify() for at findById() og save() i repository ble kalt med de riktige parameterne.

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

Sjekke et unntak i tjenesten

Denne testen verifiserer at tjenesten kaster en ApiException hvis Book som skal oppdateres ikke finnes 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)); }

Først opprettes et BookRequestDTO objekt med data for oppdateringen, og en testbok-Id som ikke finnes i databasen "999" tildeles. Metoden bookRepository.findById(idTest) blir mock'et til å returnere Optional.empty(), noe som indikerer at det ikke finnes noen bok med den Id-en.

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

Testen bruker assertThrows()-metoden for å kontrollere at når kallet bookService.updateBook(idTest, bookRequestDTO) utføres, blir en ApiException kastet.

Deretter verifiserer assertEquals()-metodene at unntaksmeldingen samsvarer med forventet tekst og at feilstatusen er lik 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 boklagrings-metoden save() ikke ble kalt, siden boken ikke ble funnet og oppdateringen ikke ble utført.

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

Sammendrag

Mockito er et bibliotek for å lage mock-objekter som lar deg simulere oppførselen til avhengigheter og isolere koden som testes. Dette forenkler testing, gjør den raskere, mer stabil og uavhengig av eksterne ressurser.

Alt var klart?

Hvordan kan vi forbedre det?

Takk for tilbakemeldingene dine!

Seksjon 5. Kapittel 3

Spør AI

expand

Spør AI

ChatGPT

Spør om hva du vil, eller prøv ett av de foreslåtte spørsmålene for å starte chatten vår

Awesome!

Completion rate improved to 3.45

bookMockito

Sveip for å vise menyen

I forrige kapittel repeterte vi hvordan man skriver enhetstester. Likevel er dette ikke alltid hensiktsmessig i vår applikasjon, fordi testing av en controller krever testing av service, og testing av service forutsetter testing av repository.

Denne avhengighetskjeden reiser spørsmålet: Må vi teste alle moduler og deres avhengigheter, eller kan vi simulere deres oppførsel — med andre ord, mocke dem?

Mockito: Introduksjon

Mockito er et populært bibliotek for Java som brukes til å lage mock-objekter i enhetstester.

Det lar utviklere simulere oppførselen til komplekse eksterne avhengigheter (som databaser, tjenester eller API-er) for å isolere og teste logikken til en spesifikk komponent (vanligvis en klasse eller metode) uten å interagere med disse eksterne systemene.

Dette gjør enhetstesting mer forutsigbar og raskere siden det ikke er behov for reelle eksterne ressurskall under testing.

Mock-objekter i Mockito

Dette gjør det mulig for utviklere å sikre at komponenten som testes (enheten) oppfører seg korrekt uten å være avhengig av reelle avhengigheter som databaser eller tredjeparts tjenester.

En klasse kan annoteres med @Mock for å opprette en mock-versjon. Denne annotasjonen brukes ofte sammen med @InjectMocks for å automatisk injisere avhengigheter i klassen som testes.

@Mock 
private MyService myServiceMock;

Dette mock-objektet kan deretter injiseres som en avhengighet i et annet objekt ved å bruke @InjectMocks-annotasjonen. Denne annotasjonen injiserer automatisk mock-objektene i klassen som testes.

@InjectMocks
private MyController myController;

Viktige metoder i Mockito

Mockito tilbyr et bredt utvalg av metoder for å håndtere mock-objekter, hver med et spesifikt formål i testprosessen. Nedenfor er de mest viktige metodene sammen med deres forklaringer.

Testing av kontrollere med Mockito

Testing av kontrollere innebærer ofte bruk av mocks for tjenester som kalles innenfor kontrolleren. I denne sammenhengen spiller Mockito en viktig rolle ved å isolere kontrollerens logikk fra tjenestelaget.

Kort oppsummering av videoen

Vi tester kontrollere ved å bruke MockMvc klassen, men hva tilbyr den og hva er fordelene ved å bruke den?

@Autowired
private MockMvc mockMvc;

Med MockMvc kan du simulere ulike HTTP-forespørsler (GET, POST, PUT, osv.), sende parametere, headere, og verifisere responser, statuskoder, headere og responsinnhold, noe som gjør enhetstesting av kontrollere mye enklere.

For eksempel kan du bruke metoder som perform() for å utføre forespørselen, andExpect() for å verifisere de forventede resultatene, og content() for å sjekke innholdet i responsen.

Test.java

Test.java

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

I tillegg kan du kjedekoble påstander for å kontrollere HTTP-status med status().isOk(), verifisere JSON-respons-strukturer med jsonPath(), og 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()));

Disse verktøyene muliggjør omfattende testing av controllerens oppførsel uten å faktisk utføre ekte HTTP-kall.

Viktige annotasjoner

Annotasjonen @WebMvcTest(BookController.class) brukes for testing av BookController, og laster kun de komponentene som er nødvendige for den spesifikke controlleren. Den utelater resten av Spring-infrastrukturen, slik at du kan fokusere på å teste controlleren i seg selv.

@WebMvcTest(BookController.class)
public class BookControllerTest 

I tillegg oppretter @MockBean-annotasjonen brukt på bookService-feltet en mock-versjon av tjenesten, slik at du kan simulere dens oppførsel. Dette bidrar til å isolere controlleren fra dens avhengigheter og gir mulighet til å fokusere på testing av logikken.

@MockBean
private BookService bookService;

Tester for updateBook-metoden

Vi har skrevet to tester for updateBook-metoden som dekker alle mulige tilfeller for denne metoden. I den første testen verifiserer vi at alt var vellykket og at vår entitet ble oppdatert.

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

Denne testen verifiserer en vellykket oppdatering av en book dersom den finnes i databasen. updateBook()-metoden til tjenesten kalles med bokens Id og et bookRequestDTO-objekt, hvorpå den returnerer et bookResponseDTO-objekt som representerer den oppdaterte boken.

Sjekk av unntakshåndtering

Det finnes også en test hvor et unntak oppstår i updateBook-metoden, og det er nødvendig å verifisere hvordan kontrolleren oppfører seg i en slik situasjon.

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

Denne testen kontrollerer kontrollerens oppførsel ved forsøk på å oppdatere en bok som ikke finnes i databasen. updateBook()-metoden til tjenesten kalles først med bokens Id og et bookRequestDTO-objekt.

I stedet for et vellykket resultat, kaster tjenesten en ApiException, som indikerer at boken med den gitte Id ikke ble funnet.

Tjenestetesting med Mockito

Ved testing av en tjeneste er det viktig å isolere den fra avhengigheter som repositories eller eksterne API-er. Mockito gjør det mulig å lage mock-objekter for disse avhengighetene og definere deres oppførsel i testene.

Kort oppsummering av videoen

Vi starter med testing av metoden når den oppdaterer data vellykket, det vil si at Id er gyldig. Vi bruker selvfølgelig Mockito for å mocke repositoryets oppførsel.

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") er mocked til å returnere en eksisterende Book fra repositoryet, og bookRepository.save(bookWithRepository) er mocked til å returnere den oppdaterte boken etter at endringene er lagret.

Metoden updateBook() for tjenesten kalles for å oppdatere boken basert på bookRequestDTO, og returnerer en BookResponseDTO.

assertNotNull(result) sikrer at resultatet ikke er null, noe som indikerer en vellykket oppdatering, mens assertEquals() kontrollerer at ID, name, author og price for den oppdaterte boken samsvarer med forventede verdier.

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

Til slutt sørger verify() for at findById() og save() i repository ble kalt med de riktige parameterne.

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

Sjekke et unntak i tjenesten

Denne testen verifiserer at tjenesten kaster en ApiException hvis Book som skal oppdateres ikke finnes 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)); }

Først opprettes et BookRequestDTO objekt med data for oppdateringen, og en testbok-Id som ikke finnes i databasen "999" tildeles. Metoden bookRepository.findById(idTest) blir mock'et til å returnere Optional.empty(), noe som indikerer at det ikke finnes noen bok med den Id-en.

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

Testen bruker assertThrows()-metoden for å kontrollere at når kallet bookService.updateBook(idTest, bookRequestDTO) utføres, blir en ApiException kastet.

Deretter verifiserer assertEquals()-metodene at unntaksmeldingen samsvarer med forventet tekst og at feilstatusen er lik 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 boklagrings-metoden save() ikke ble kalt, siden boken ikke ble funnet og oppdateringen ikke ble utført.

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

Sammendrag

Mockito er et bibliotek for å lage mock-objekter som lar deg simulere oppførselen til avhengigheter og isolere koden som testes. Dette forenkler testing, gjør den raskere, mer stabil og uavhengig av eksterne ressurser.

Alt var klart?

Hvordan kan vi forbedre det?

Takk for tilbakemeldingene dine!

Seksjon 5. Kapittel 3
some-alt