• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Dlaczego mam taki wynik - testowanie jednostkowe - SpringBoot

Object Storage Arubacloud
0 głosów
500 wizyt
pytanie zadane 12 grudnia 2018 w Java przez must Bywalec (2,980 p.)

Cześć. Mam pewien problem, otóż chce przetestować metodę wyszukującą książki po kategorii. Napisałem już test, który jest "poprawny", czyli znajduje książkę i wynik jest taki sam z wynikiem oczekującym.

Chciałem napisać test, gdzie nie znajduje tej książki. Jednakże wynik oczekiwany z wynikiem aktualnym się nie zgadza. 

Oto cały test:

@RunWith(SpringRunner.class)
@WebMvcTest(BookFindOperationsController.class)
public class BookFindOperationsControllerTest {


    @Autowired
    MockMvc mockMvc;

    @MockBean
    BookFindOperationsService bookService;


@Test
    public void shouldReturnNoBooksByFindBooksByCategory() throws Exception {
        List<Book> books = new ArrayList<>();
        Book book = new Book("W pustynzi i w puszczy", "Henryk Sienkiewicz", "dramat", true);
        books.add(book);

        when(bookService.findBooksByCategory("test")).thenReturn(books);

        String expected = "[]";

        MvcResult mvcResult = mockMvc.perform(get("/books/category/test")
                .contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andReturn();

        String content = mvcResult.getResponse().getContentAsString();

        assertEquals(expected, content);

        verify(bookService,times(1)).findBooksByCategory(anyString());

    }

Konsola:

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /books/category/test
       Parameters = {}
          Headers = {Content-Type=[application/json;charset=UTF-8]}

Handler:
             Type = bookrental.controller.book.BookFindOperationsController
           Method = public java.util.List<bookrental.model.book.Book> bookrental.controller.book.BookFindOperationsController.findBooksByCategory(java.lang.String)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = {Content-Type=[application/json;charset=UTF-8]}
     Content type = application/json;charset=UTF-8
             Body = [{"id":0,"title":"W pustynzi i w puszczy","author":"Henryk Sienkiewicz","category":"dramat","available":true}]
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

org.junit.ComparisonFailure: 
Expected :[]
Actual   :[{"id":0,"title":"W pustynzi i w puszczy","author":"Henryk Sienkiewicz","category":"dramat","available":true}]
 <Click to see difference>


	at org.junit.Assert.assertEquals(Assert.java:115)
	at org.junit.Assert.assertEquals(Assert.java:144)
	at bookrental.controller.book.BookFindOperationsControllerTest.shouldReturnNoBooksByFindBooksByCategory(BookFindOperationsControllerTest.java:159)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
	at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
	at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

I tego nie rozumiem, przecież dodaje obiekt książki, w którym kategoria to jest dramat, a chcąć wyszukać książkę po kategorii wpisuje test, mimo to znajduje mi dalej tę książke. Co robie źle?

Metoda, którą chce przetestować.

@GetMapping("/books/category/{categoryID}")
    public List<Book> findBooksByCategory(@PathVariable String categoryID) {
        return bookService.findBooksByCategory(categoryID);
    }

 

2 odpowiedzi

+2 głosów
odpowiedź 12 grudnia 2018 przez Aisekai Nałogowiec (42,190 p.)
wybrane 12 grudnia 2018 przez must
 
Najlepsza

Wydaje mi się, że nie do końca wiesz o co chodzi z Mockowaniem oraz Testowaniem.

Po pierwsze: co ten test ma sprawdzać? Czy Mock działa? Działa. Ale co w związku z tym? Nie ma sensu pisać testów jednostkowych do każdej metody, zwłaszcza gdy ta metoda nie ma żadnej logiki a wykonuje zwykłe operacje Crudowe.

Po drugie:

        when(bookService.findBooksByCategory("test")).thenReturn(books);

Sprawia, że w chwili gdy na obiekcie bookService zostanie wywołana metoda z parametrem "test" zostanie zwrócony obiekt books (w tym przypadku List<Book>).

komentarz 12 grudnia 2018 przez must Bywalec (2,980 p.)
Ale ja nie rozumiem jednej rzeczy.

Ksiązka ma kategorię blabla, w when jako parametr przesyłam "test", zwracam listę książek, czyli książke w której jest kategoria blabla, tak?

A chciałem przetestować, czy jak podam inną kategorię niż ma książka to nie zwróci mi nic....
komentarz 12 grudnia 2018 przez Aisekai Nałogowiec (42,190 p.)

Pobierając z @PathVariable Stringa "test" i przesyłając ten parametr do metody Mocka bookService findBooksByCategory("test") sprawiasz, że zostaje zwrócona Lista z książkami (books) w której masz książkę book. 

Innymi słowy:

bookService jest u Ciebie Mockiem, który podczas wywoływania metody findBooksByCategory() z parametrem "test", zwraca books. To, że w liście books masz książki które mają kategorię "dramat" czy "blablabla" to nie ma znaczenia. 

@GetMapping("/books/category/{categoryID}")
    public List<Book> findBooksByCategory(@PathVariable String categoryID) {
        return bookService.findBooksByCategory(categoryID); //<----- w tym miejscu masz zmockowanego bookService. 
    }

 

komentarz 12 grudnia 2018 przez must Bywalec (2,980 p.)

A jak mam:

MvcResult mvcResult = mockMvc.perform(get("/books/category/zz")
                .contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andReturn();

To tutaj odwołuje się do parametru "zz" czyli do zupełnie czego innego tak i nie zwróci mi tutaj books? Nie rozumiem dlaczego tę linijkę z when miałbym usunąć.

Mam test też taki, który testuje to jak jest wszystko poprawne:

@Test
    public void findBooksByCategory() throws Exception {
        List<Book> books = new ArrayList<>();
        Book book = new Book("W pustyni i w puszczy", "Henryk Sienkiewicz", "dramat", true);
        books.add(book);

        when(bookService.findBooksByCategory(anyString())).thenReturn(books);


        String expected = "[{\"id\":0,\"title\":\"W pustyni i w puszczy\",\"author\":\"Henryk Sienkiewicz\",\"category\":\"dramat\",\"available\":true}]";


        MvcResult mvcResult = mockMvc.perform(get("/books/category/dramat")
                .contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andReturn();

        String content = mvcResult.getResponse().getContentAsString();

        assertEquals(expected, content);

        verify(bookService,times(1)).findBooksByCategory(anyString());
    }

I niby tutaj też pownienem when usunąć?

komentarz 12 grudnia 2018 przez Aisekai Nałogowiec (42,190 p.)

Twoje when() w tym przypadku działa, tylko dlatego że podałeś anyString() jako parametr. Równie dobrze mógłbyś w 13 linijce zmienić na:

MvcResult mvcResult = mockMvc.perform(get("/books/category/blablabla")
                .contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
             
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andReturn();

I tak Ci zwróci wszystkie books. Problem jest w tym, że jedyne co usiłujesz testować to operacji Crudowe kompletnie bez logiki - jaki jest sens testowania czy zostają pobrane dane z BD, w chwili gdy tą bazę danych Mockujesz? Kontrolery testujesz pod kątem, czy "dana strona działa". Natomiast serwisy (warstwę odpowiedzialną za Model w MVC) możesz testować jednostkowo. Bawienie się w MvcResult i sprawdzanie czy Stringi są poprawne (w sensie expected i result) to imo chory pomysł. 

komentarz 12 grudnia 2018 przez must Bywalec (2,980 p.)

Może i chory, ale przynajmniej wpajam sobie mniej więcej jak to wygląda. Także nic nie stracę na tym.

Wracając do przykładu z pytania.

W takim razie, ma to wyglądać tak:


@RunWith(SpringRunner.class)
@WebMvcTest(BookFindOperationsController.class)
public class BookFindOperationsControllerTest {
 
 
    @Autowired
    MockMvc mockMvc;
 
    @MockBean
    BookFindOperationsService bookService;
 
 
@Test
    public void shouldReturnNoBooksByFindBooksByCategory() throws Exception {
        List<Book> books = new ArrayList<>();
        Book book = new Book("W pustynzi i w puszczy", "Henryk Sienkiewicz", "dramat", true);
        books.add(book);
 
        when(bookService.findBooksByCategory("test")).thenReturn(books);
 
        String expected = "[]";
 
        MvcResult mvcResult = mockMvc.perform(get("/books/category/blabla")
                .contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andReturn();
 
        String content = mvcResult.getResponse().getContentAsString();
 
        assertEquals(expected, content);
 
        verify(bookService,times(1)).findBooksByCategory(anyString());
 
    }

?

Nie rozumiem po prostu tego:

MvcResult mvcResult = mockMvc.perform(get("/books/category/blabla")...

Bo przecież tutaj podaje konkretną kategorię jaką jest blabla. Jaki ma sens dawania tam czegokolwiek np. blabla?

I jak mam blabla to co się wykonuje w takim razie tutaj? 

komentarz 12 grudnia 2018 przez Aisekai Nałogowiec (42,190 p.)

Mówiłem o tym drugim przykładzie i po prostu chciałem Ci pokazać po prostu, że masz źle napisany test. Jakąkolwiek kategorię byś nie podał (nawet Blablabla) to i tak zostanie Ci zwrócona Lista a w niej: 

Book book = new Book("W pustynzi i w puszczy", "Henryk Sienkiewicz", "dramat", true);

dlatego, że zmockowałeś bookService i metodę findBookByCategory(anyString()). 

Możesz sobie w when() dać, np "blablabla" i jako return zwrócić pustą listę, albo możesz po prostu wywalić when() - wtedy zwrócony zostanie po prostu null zamiast listy. Oba przypadki to imo zły pomysł.

PS: Stracisz. O tyle, że od początku uczysz się złych praktyk zamiast uczyć się tych dobrych. Książka jest pewnie bardzo stara, bo widzę że wiążesz przez pole, co też jest złą praktyką. Wiąż przez konstruktor, bo inaczej uniemożliwiasz wygodne testowanie.

komentarz 12 grudnia 2018 przez must Bywalec (2,980 p.)
edycja 12 grudnia 2018 przez must

No tak zgodzę się, bo dałem anyString(), więc mogę dać byle co.

A w przykładzie z pytania, mam taki test i już przechodzi:

@Test
    public void shouldReturnNoBooksByFindBooksByCategory() throws Exception {
        List<Book> books = new ArrayList<>();
        Book book = new Book("W pustynzi i w puszczy", "Henryk Sienkiewicz", "dramat", true);
        books.add(book);

        when(bookService.findBooksByCategory("dramat")).thenReturn(books);

        String expected = "[]";

        MvcResult mvcResult = mockMvc.perform(get("/books/category/test2")
                .contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andReturn();

        String content = mvcResult.getResponse().getContentAsString();

        assertEquals(expected, content);

        verify(bookService,times(1)).findBooksByCategory(anyString());
    }

Już wiem, że nie powinienem testować kontrolera, ale wole sobie utrwalić jak jakikolwiek test się pisze.

PS: ...bo widzę że wiążesz przez pole, co też jest złą praktyką. Wiąż przez konstruktor, bo inaczej uniemożliwiasz wygodne testowanie.

Mówisz o @Autowired?

W kontrolerach i we wszystkim mam:

private final BookRepository bookRepository;

@Autowired
public BookCrudOperationsService(BookRepository bookRepository) {
    this.bookRepository = bookRepository;
}

Tak mi środowisko podopowiada, że tak powiinenem robić.

+2 głosów
odpowiedź 12 grudnia 2018 przez Mateusz51 Nałogowiec (28,180 p.)
Zrobiles w 19 linijce mocka ktory zwraca Ci books gdy dostaje zmienna test i wywolujesz url z test. Zwraca Ci tak jak powinno. Masz blad w tescie
komentarz 12 grudnia 2018 przez must Bywalec (2,980 p.)
W takim razie w 23 mam dać co innego? To jaką kategorię wpisałem dla ksiązki jest nieważne tak?
1
komentarz 12 grudnia 2018 przez Mateusz51 Nałogowiec (28,180 p.)
Mockito dziala tak ze dla wywolania metody z danymi argumnetami zwraca Ci to co podales. Kod tej funkcji nigdy sie nie aktywowal. Wiec to co podales w ksiazce nie ma zadnego znaczenia oraz kod tej metody rowniez.

 

Szczerze powiedziawszy próbujesz przetestowac prosta operacje typu crud. Tak naprawde w takim tescie nie testujesz zadnej logiki. Tylko to czy framework i jezyk dziala. Ale poczatki takie sa.
komentarz 12 grudnia 2018 przez Aisekai Nałogowiec (42,190 p.)
19 linijkę po prostu usuń, bo jest zbędna i zaśmieca kod.
komentarz 12 grudnia 2018 przez must Bywalec (2,980 p.)
@Mateusz51

Ksiązka ma kategorię blabla, w when jako parametr przesyłam "test", zwracam listę książek, czyli książke w której jest kategoria blabla, tak?

A chciałem przetestować, czy jak podam inną kategorię niż ma książka to nie zwróci mi nic....
komentarz 13 grudnia 2018 przez Mateusz51 Nałogowiec (28,180 p.)
Odpowiadając na twoje pytanie to tak zostanie zwrócona książka kategorii blabla:)

Z racji tego że nie masz logiki w swojej aplikacji twoje testy są trochę karkołomne. Jeśli chciałbyś za symulować prawdziwe działanie aplikacji i wyszukiwanie to zamiast mocka powinieneś zainteresować się bazami danych w pamięci. Działa to tak że przy uruchomieniu testu tworzona jest na czas testu baza danych.
https://www.baeldung.com/spring-jpa-test-in-memory-database
komentarz 13 grudnia 2018 przez must Bywalec (2,980 p.)
Mam bazę danych Apache i dane są inicjowane wraz z włączeniem aplikacji. Ale jak się odnieść do tych danych w teście?

Podobne pytania

0 głosów
2 odpowiedzi 1,087 wizyt
0 głosów
0 odpowiedzi 83 wizyt
0 głosów
1 odpowiedź 322 wizyt
pytanie zadane 28 maja 2019 w Java przez JuniorPL Użytkownik (770 p.)

92,567 zapytań

141,420 odpowiedzi

319,615 komentarzy

61,952 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...