Wątek przeniesiony 2023-10-16 16:02 z Java przez Riddle.

Czy test sprawdzający wyjątek odnośnie nieudanej próby logowania powinien robić asercje na wiadomości?

0

Cześć,
Mam takie pytanie. Stworzyłem endpoint do logowania, w przypadku kiedy pola 'username' i 'password' będą puste, program powinien rzucić wyjątek z wiadomością co jest nie tak. Napisałem taki test integracyjny, który sprawdza czy tak się dzieje:

@Test
void shouldReturnBadRequestForEmptyUsernameAndPasswordDuringLogin() throws Exception {
    final var requestBody = "{\"usernameOrEmail\": \"\",\"password\": \"\"}";

    mockMvc.perform(post("/api/v1/auth/login")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(requestBody))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.message").value("Validation failed"))
            .andExpect(jsonPath("$.errors").exists())
            .andExpect(jsonPath("$.errors.usernameOrEmail").value(
                    "The usernameOrEmail field cannot be empty"))
            .andExpect(jsonPath("$.errors.password").value(
                    "The password field cannot be empty"));
}

Zastanawiam się, czy pisząc testy, powinienem sprawdzać dokładnie jaka wiadomość jest rzucana, czy może skupić się bardziej na generalnym podejściu, sprawdzając, czy status jest zgodny z oczekiwanym a wiadomość błędu nie jest pusta.

Dzięki za pomoc.

0

Nie ma jednoznacznej odpowiedzi na to pytanie.

Zanim odpowiedziałbym na to pytanie, mam inne:

  • Czemu spróbowanie zalogowania się na credentiale ""/"" ma się kończyć błędem? Czemu nie potraktować tego po prostu jak próba logowania ze złym hasłem, lub logowania na konto nieistniejącego użytkownika? Innymi słowy nie widzę powodu czemu wartości "" miałyby być traktowane jakoś "specjalnie"?
  • Czemu nieudane próby logowania mają się kończyć wyjątkiem? Czemu nie jakąś wiadomością zwrotną, typu "login failed", a jeśli to rest to jakieś 403/422/401, etc.
  • Jeśli już koniecznie to ma być wyjątek, to czy na prawdę chcesz pokazywać aż tyle informacji w odpowiedzi?

I dopiero jak będą odpowiedzi na te pytania, to się można zastanawiać jak powinny wyglądać testy pod ten endpoint.

Ale jako zasada, to ogólnie testy mogą sprawdzać message wyjątku, ale nie muszą - zależy jak to sobie napiszesz. Od siebie dodam, że powinieneś mieć jeden test który sprawdza czy logowanie jest nie udane, a drugi który sprawdza message (po prostu rozbiłbym to na osobne testy).

0
Riddle napisał(a):
  • Czemu spróbowanie zalogowania się na credentiale ""/"" ma się kończyć błędem? Czemu nie potraktować tego po prostu jak próba logowania ze złym hasłem, lub logowania na konto nieistniejącego użytkownika? Innymi słowy nie widzę powodu czemu wartości "" miałyby być traktowane jakoś "specjalnie"?

Stworzyłem record LoginRequest, który jest parametrem metody login(). Żeby sprawdzić, czy pola nie są puste, dodałem adnotacje @NotNull, @NotEmpty do pól tego recordu wraz z niestandardowymi wiadomościami. W controllerze użyłem adnotacji @Valid i @RequestBody. Podczas próby logowania z pustym polem, rzucało mi wyjątek MethodArgumentNotValidException, który zawierał wiadomość z recordu, co poszło nie tak. Stąd ten błąd.

public record LoginRequest(
         @NotNull(message = "The usernameOrEmail field cannot be null")
         @NotEmpty(message = "The usernameOrEmail field cannot be empty")
         String usernameOrEmail,
         @NotNull(message = "The password field cannot be null")
         @NotEmpty(message = "The password field cannot be empty")
         String password) {
}
Riddle napisał(a):
  • Czemu nieudane próby logowania mają się kończyć wyjątkiem? Czemu nie jakąś wiadomością zwrotną, typu "login failed", a jeśli to rest to jakieś 403/422/401, etc.

W innych przypadkach takich jak logowanie z błędnym hasłem/loginem zrobiłem to tak jak napisałeś. Tutaj przykład testu na błędne hasło:

@Test
void shouldReturnUnauthorizedAndErrorMessageForInvalidPasswordLoginWhenEmailIsCorrect() throws Exception {
    final var requestBody =
            "{\"usernameOrEmail\": \"[email protected]\",\"password\": \"testWrongPassword@123\"}";

    mockMvc.perform(post("/api/v1/auth/login")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(requestBody))
            .andExpect(status().isUnauthorized())
            .andExpect(content().string("Incorrect username/email or password"));
}
Riddle napisał(a):
  • Jeśli już koniecznie to ma być wyjątek, to czy na prawdę chcesz pokazywać aż tyle informacji w odpowiedzi?

Ciężko mi odpowiedzieć na to pytanie. Dajmy na to taki przykład - rejestracja użytkownika. Zakładam, że login może mieć tylko duże/małe litery i cyfry. Email powinien być zgodny ze standardowym formatem. Długość hasła powinna być nie mniejsza niż 8 znaków i musi zawierać co najmniej jedną dużą i małą literę, cyfrę i znak specjalny. Ktoś próbuję się zarejestrować z takimi danymi:

login = example%21 - błędny login, zawiera znak specjalny
email = example#gmail.com - email niezgodny ze standardowym formatem adresu email
hasło = Example123 - brak znaku specjalnego

Tutaj muszę przekazać użytkownikowi, że w każdym polu wpisał dane w nieprawidłowym formacie. U mnie odpowiedź z postmana wygląda tak:

"errors": {
    "password": "Invalid password format. The password must contain an least 8 characters, including uppercase letters, lowercase letters, numbers, and special characters.",
    "email": "Invalid email address format. The email should follow the standard format (e.g., [email protected]).",
    "username": "Invalid username format. The username can contain only letter and numbers."
}

Jest to dużo informacji, ale wydaje mi się, że są one niezbędne do przekazania użytkownikowi, co poszło nie tak. Dokładnie takie samo podejście miałem z loginem.

0
Ornstein napisał(a):
Riddle napisał(a):
  • Czemu spróbowanie zalogowania się na credentiale ""/"" ma się kończyć błędem? Czemu nie potraktować tego po prostu jak próba logowania ze złym hasłem, lub logowania na konto nieistniejącego użytkownika? Innymi słowy nie widzę powodu czemu wartości "" miałyby być traktowane jakoś "specjalnie"?

Stworzyłem record LoginRequest, który jest parametrem metody login(). Żeby sprawdzić, czy pola nie są puste, dodałem adnotacje @NotNull, @NotEmpty do pól tego recordu wraz z niestandardowymi wiadomościami. W controllerze użyłem adnotacji @Valid i @RequestBody. Podczas próby logowania z pustym polem, rzucało mi wyjątek MethodArgumentNotValidException, który zawierał wiadomość z recordu, co poszło nie tak. Stąd ten błąd.

public record LoginRequest(
         @NotNull(message = "The usernameOrEmail field cannot be null")
         @NotEmpty(message = "The usernameOrEmail field cannot be empty")
         String usernameOrEmail,
         @NotNull(message = "The password field cannot be null")
         @NotEmpty(message = "The password field cannot be empty")
         String password) {
}

To mówisz o szczegółach implementacyjnych. Nieistotne z punktu widzenia testu.

Riddle napisał(a):
  • Jeśli już koniecznie to ma być wyjątek, to czy na prawdę chcesz pokazywać aż tyle informacji w odpowiedzi?

Ciężko mi odpowiedzieć na to pytanie. Dajmy na to taki przykład - rejestracja użytkownika. Zakładam, że login może mieć tylko duże/małe litery i cyfry. Email powinien być zgodny ze standardowym formatem. Długość hasła powinna być nie mniejsza niż 8 znaków i musi zawierać co najmniej jedną dużą i małą literę, cyfrę i znak specjalny. Ktoś próbuję się zarejestrować z takimi danymi:

login = example%21 - błędny login, zawiera znak specjalny
email = example#gmail.com - email niezgodny ze standardowym formatem adresu email
hasło = Example123 - brak znaku specjalnego

Tutaj muszę przekazać użytkownikowi, że w każdym polu wpisał dane w nieprawidłowym formacie. U mnie odpowiedź z postmana wygląda tak:

"errors": {
    "password": "Invalid password format. The password must contain an least 8 characters, including uppercase letters, lowercase letters, numbers, and special characters.",
    "email": "Invalid email address format. The email should follow the standard format (e.g., [email protected]).",
    "username": "Invalid username format. The username can contain only letter and numbers."
}

Jest to dużo informacji, ale wydaje mi się, że są one niezbędne do przekazania użytkownikowi, co poszło nie tak. Dokładnie takie samo podejście miałem z loginem.

No to to wygląda, że zwracanie informacji o tym, że przekazane są nieprawdziwe są elementem logiki, więc powinieneś napisać pod nie test. Moim zdaniem, oczywiście.

Aczkolwiek te testy, nie powiedziałbym że są szczególnie dobre, bo bardzo polegają na springu, przez co są rigid.

0

Tylko jeśli te testy wg ciebie nie są szczególnie dobre, to jaka jest alternatywa (w przypadku Springa)? W jednym projekcie zdecydowaliśmy się na zupełne pominięcie testów kontrolerów webowych, bo pieprzenie się z tym było straszne, a logiki było tam tyle co nic (99% przypadków wywołanie serwisu i ew. zmapowanie kodu błędu jeśli w ogóle, albo jakieś konwersje typów), i uważam że to była w tym konkretnym przypadku fantastyczna decyzja.

2
kelog napisał(a):

Tylko jeśli te testy wg ciebie nie są szczególnie dobre, to jaka jest alternatywa (w przypadku Springa)? W jednym projekcie zdecydowaliśmy się na zupełne pominięcie testów kontrolerów webowych, bo pieprzenie się z tym było straszne, a logiki było tam tyle co nic (99% przypadków wywołanie serwisu i ew. zmapowanie kodu błędu jeśli w ogóle, albo jakieś konwersje typów), i uważam że to była w tym konkretnym przypadku fantastyczna decyzja.

Testy kontrolera powinny być, (żeby sprawdzić pathy, return code'y, parsowanie argumentów, sprawdzenie mime-typów, i jakąkolwiek logikę zwiazaną z http jaką masz - jeśli jakąś masz). Chodzi np o to że jak ktoś zmieni path albo metodę, to jakiś test się powinien wywalić.

Aczkolwiek, z czym się zgadzam - testami kontrollerów nie powinno się testować logiki aplikacji - i mam wrażenie że dlatego mówisz że była to "fantastyczna decyzja" - bo większość ludzi myśli że testami kontrolerów powinno się testować logikę, próbuje to robić, nie udaje się - i dochodzą do wnisoku że testy są słabe.

I tutaj może rozprawię się z miskoncepcją - to że kontroler nie powinien testować logiki aplikacji nie oznacza że nie powinien wywołać logiki aplikacji. Może ją wywołać, może jej użyć, może na niej polegać - nie musisz mockować serwisów - możesz wsadzić normalne, poprawne implementacje. Tylko nie powinien jej testować - innymi słowy nie powinien robić asercji na poprawnośc wyników pod względem poprawności biznesowej, a jedynie asercje pod względem zgodności z protokołem HTTP. Jak masz np kontroler z pathem /shortest-path który ma zwrócić najkrótszą ścieżkę w jakimś labiryncie, i ten kontroler woła ShortestPathService, to w teście tego kontrolera możesz normalnie wsadzić prawdziwą implementację ShortestPathService, nie musisz mocka. Ale nie rób asercji, czy faktycznie najkrótsza ścieżka została zwrócona, zrób tylko asercję czy pola mają dobrą nazwę, czy status code'y są odpowiednie, czy dostaniesz Bad Request jak wyślesz malformed request. To czy ścieżka faktycznie jest najkrótsza, jakieś edge case'y, to sprawdź w innym teście - takim który nie wie nic o kontrolerze.

Dobre testy, według mnie, wyglądałyby tak, że:

  • należałoby mieć dwa moduły:
    • jeden z controllerami w springu, i jeden z logiką aplikacji (i powinny być tak zrobione że w logice aplikacji nie ma nic odn. http, żadnych jsonów, kodów, statusów, etc.) - i do tego modułu nalezy normalnie napisać testy, nie wymagają nawet springa i mvc, tylko sam junit i asercje.
    • drugi, który jest samymi kontrolerami - i tu nie powinno być logiki związanej z domeną - a tylko logika związana z http - i tutaj powinny być testy też, ale powinno być ich mało, być krótkie i z reguły wystarczy jeden per endpoint. Tylko te testy powinny być napisane tak, żeby nie miały silnego przywiązania do springa najlepiej. Powinny wyglądać jakoś tak:
      • @Test
        void testLoginEndpoint() {
          assertEndpointReturns("/login", emptyMap(), 422);
        }
        @Test
        void testLoginEndpointLogin() {
          assertEndpointReturns("/login", map("login", "user", "password", "123"), 200);
        }
        
        i potem oczywiście te metody jakoś zaimplementować, również to może być mockMvc, albo inny sposób.
0
Riddle napisał(a):
kelog napisał(a):

Tylko jeśli te testy wg ciebie nie są szczególnie dobre, to jaka jest alternatywa (w przypadku Springa)? W jednym projekcie zdecydowaliśmy się na zupełne pominięcie testów kontrolerów webowych, bo pieprzenie się z tym było straszne, a logiki było tam tyle co nic (99% przypadków wywołanie serwisu i ew. zmapowanie kodu błędu jeśli w ogóle, albo jakieś konwersje typów), i uważam że to była w tym konkretnym przypadku fantastyczna decyzja.

Testy kontrolera powinny być, (żeby sprawdzić pathy, return code'y, parsowanie argumentów, sprawdzenie mime-typów, i jakąkolwiek logikę zwiazaną z http jaką masz - jeśli jakąś masz). Chodzi np o to że jak ktoś zmieni path albo metodę, to jakiś test się powinien wywalić.

Aczkolwiek, z czym się zgadzam - testami kontrollerów nie powinno się testować logiki aplikacji - i mam wrażenie że dlatego mówisz że była to "fantastyczna decyzja" - bo większość ludzi myśli że testami kontrolerów powinno się testować logikę, próbuje to robić, nie udaje się - i dochodzą do wnisoku że testy są słabe.

I tutaj może rozprawię się z miskoncepcją - to że kontroler nie powinien testować logiki aplikacji nie oznacza że nie powinien wywołać logiki aplikacji. Może ją wywołać, może jej użyć, może na niej polegać - nie musisz mockować serwisów - możesz wsadzić normalne, poprawne implementacje. Tylko nie powinien jej testować - innymi słowy nie powinien robić asercji na poprawnośc wyników pod względem poprawności biznesowej, a jedynie asercje pod względem zgodności z protokołem HTTP. Jak masz np kontroler z pathem /shortest-path który ma zwrócić najkrótszą ścieżkę w jakimś labiryncie, i ten kontroler woła ShortestPathService, to w teście tego kontrolera możesz normalnie wsadzić prawdziwą implementację ShortestPathService, nie musisz mocka. Ale nie rób asercji, czy faktycznie najkrótsza ścieżka została zwrócona, zrób tylko asercję czy pola mają dobrą nazwę, czy status code'y są odpowiednie, czy dostaniesz Bad Request jak wyślesz malformed request. To czy ścieżka faktycznie jest najkrótsza, jakieś edge case'y, to sprawdź w innym teście - takim który nie wie nic o kontrolerze.

Dobre testy, według mnie, wyglądałyby tak, że:

  • należałoby mieć dwa moduły:
    • jeden z controllerami w springu, i jeden z logiką aplikacji (i powinny być tak zrobione że w logice aplikacji nie ma nic odn. http, żadnych jsonów, kodów, statusów, etc.) - i do tego modułu nalezy normalnie napisać testy, nie wymagają nawet springa i mvc, tylko sam junit i asercje.
    • drugi, który jest samymi kontrolerami - i tu nie powinno być logiki związanej z domeną - a tylko logika związana z http - i tutaj powinny być testy też, ale powinno być ich mało, być krótkie i z reguły wystarczy jeden per endpoint. Tylko te testy powinny być napisane tak, żeby nie miały silnego przywiązania do springa najlepiej. Powinny wyglądać jakoś tak:
      • @Test
        void testLoginEndpoint() {
          assertEndpointReturns("/login", emptyMap(), 422);
        }
        @Test
        void testLoginEndpointLogin() {
          assertEndpointReturns("/login", map("login", "user", "password", "123"), 200);
        }
        
        i potem oczywiście te metody jakoś zaimplementować, również to może być mockMvc, albo inny sposób.

Mam pytanie. Korzystając z adnotacji @Valid w kontrolerze, również tam odbywa się walidacja moich pól. Żeby zrobić te testy tak jak napisałeś, wychodzi na to, że muszę przenieś walidację do serwisu i zrezygnować z używania adnotacji @Valid. Dobrze rozumiem?

2
Ornstein napisał(a):

Mam pytanie. Korzystając z adnotacji @Valid w kontrolerze, również tam odbywa się walidacja moich pól. Żeby zrobić te testy tak jak napisałeś, wychodzi na to, że muszę przenieś walidację do serwisu i zrezygnować z używania adnotacji @Valid. Dobrze rozumiem?

Bardzo dobre pytanie, i tutaj pojawia się całe serce problemu. Od decyzji którą teraz podejmiesz, będzie zależała jakość Twojej aplikacji.

Pytanie brzmi: "Co sprawdzasz tą adnotacją @Valid"? Czy sprawdzasz rzeczy które są apliaction-specific (zależą od natury aplikacji, czy jest webowa, cli, desktopwa, mobilna etc.), czy zależą od biznesowych kryteriów?

Jeśli @Valid sprawdza takie rzeczy jak:

  • To czy int ma określoną wielkośc
  • Czy argument jest w body, w query, czy w headerach
  • Sprawdza format (np json, xml, yaml, etc.)
  • Sprawdza mime type z headerów
  • To czy niektóre atrybuty spełniają jakieś kryteria zalezne od formy, np czy int jest ujemny
  • Sprawdzenie wypełnienia captchy
  • Sprawdzenie CSRF
  • Czy otrzymany JSON/XML nie jest przypadkiem malformed
  • Sprawdzenie takich rzeczy jak nagłówki Host, Remote, Origin, CORS etc.
  • Czy hasło użytkownika jest stringiem, a nie intem

to test pod to powinien znajdować się w teście kontrolera, bo te rzeczy są związane z interfejsem komunikacyjnym, w tym wypadku HTTP. To są rzeczy na które użytkownik (nietechniczny) nie zwróci uwagi. Dla niego mogłyby być, mogłyby nie być, nie ma to znaczenia. (ma znaczenie z technicznych powodów - musimy je dodać, ale nie dla użytkownika).

Jeśli natomiast Twój @Valid miałby sprawdzać takie rzeczy jak:

  • To czy użytkownik jest zalogowany
  • czy jego nick się składa z odpowiednich znaków
  • czy jakieś pole (jak login) jest unikalne w aplikacji
  • podczas pokazania jakiegoś zasobu, to czy ten zasób istnieje
  • czy dane które podał są poprawne z uwagi na ich treść (nie formę), jak np poprawny pesel
  • czy użytkownik ma np 18 lat żeby zobaczyć jakąś treść
  • czy ma na tyle punktów/reputacji żeby wykonać akcję
  • czy hasło użytkownika się zgadza i jest poprawne

to testy pod nie powinny się znaleźć w teście logiki - bo te rzeczy są poprawne, i nie zależą od interfejsu tylko od natury Twojej aplikacji. To są rzeczy na które klient/użytkownik zwróci uwagę.

Jesli tak się dzieje że Twoja adnotacja @Valid sprawdza obie te rzeczy: tzn. trochę tego trochę tego, np sprawdza poprawność typów ORAZ tego czy zasób istnieje w bazie; to najlepiej byłoby to rozbić na dwa byty - to co jest wspólnego z HTTP zostawić w @Valid, a to co jest niezwiązane z interfejsem przenieść do serwisów. Nie jest to proste do zrobienia - wymaga od Ciebie mentalnego wysiłku, żeby się zastanowić hmm... to pole tutaj, to jest część interfejsu, czy część biznesów?. I ten właśnie wysiłek mentalny który włożysz w rozkmienienie tego, to jest to co zwiększy jakość Twojej aplikacji.

Weź pod uwagę, że te rzeczy nie są nigdy 1:1. To od Ciebie zależy jak je podzielisz. Np napisałem że Sprawdza format (np json, xml, yaml, etc.) to jest elementu interfejsu HTTP, bo tak jest w większości przypadków. Ale jeśli pracowałbym np na edytorem JSON (coś jak notepad++), to wtedy poprawność składni jaknajbardziej byłaby logiką biznesową, i jej sprawdzenie nie powinno być w kontrolerze. Napisałem też np że To czy int ma określoną wielkośc to jest element interfejsu HTTP; bo tak jest w większości aplikacji. Ale jeśli pisalibyśmy np apkę pomagającą w konwersji typów (np jakiś konwrter z C na JS), to wtedy wielkość liczb byłaby bardzo ważna! Trzeba by koniecznie umieścić to w logice biznesowej. Działa to też w drugą stronę, napisałem że format nicku to logika biznesowa czy jego nick się składa z odpowiednich znaków; ale oczywiście możemy mieć interfejs który pozwala tylko na konkretne znaki, i wtedy ich format to powinien być artefakt interfejsu - i powinien być w kontrolerze. Więc widzisz, cała ta rozdziałka to jest Twój obowiązek, na zdecydowanie które zasady są "dlatego że muszą być" (logika biznesowa), a które są "dlatego że korzystamy z HTTP" (logika kontrolera). Do Ciebie należy zdecydowanie o tym, po co dodajesz jakiś kod. Jeśli dodajesz kod, dlatego że klient Cię poprosił o funkcjonalnośc (np "jeśli nie ma zasobu, pokaż błąd") to ten test i implementacja powinny być do serwisu. Jeśli dodajesz kod tylko pod HTTP (bo np dostałeś błąd z Tomcata'a albo z konsoli przeglądarki, o którym user nie ma pojęcia) to powinno iść do testu i logiki kontrolera.

Tak powinno być w odpowiednio napisanej aplikacji, gdzie przestrzega się dependency inversion.

Niestety, większość (jak nie wszystkie) frameworków (Spring, laravel, django, railsy, większość z nich) bardzo lubi mieszać interfejs http z bazą, więc dadzą Ci do dyspozycji takie rozwiązania jak @Valid w Spring (albo np Request w Laravelu), którym bardzo łatwo można już w samym kontrolerze sprawdzić czy jakiś model ORM istnieje; tylko że to niestety niejawnie dodaje nam tight-coupling pomiędzy HTTP i bazą, niestety.

Więc tu się pojawia pytanie kolejne: Czy wolisz skorzystać z wygody (i tymczasowej szybkości) którą daje Ci spring tą adnotacją i zrobić to "bad and dirty"; czy wolisz napisać to w dwóch krokach (raz w kontrolerze, raz w logice z odpowiednimi testami), co zajmie trochę więcej czasu teraz - ale za to będzie jakby lepsze na długą metę. Decyzja należy do Ciebie (ja praktycznie zawsze staram się wybierać to drugie).

Więcej na ten temat możesz się dowiedzieć z tego filmiku The Principles of Clean Architecture.

1

Teraz rozumiem, fajnie to wyjaśniłeś. Dzięki

Edit.
@Riddle:
Napisałeś:

Dobre testy, według mnie, wyglądałyby tak, że:

należałoby mieć dwa moduły:
jeden z controllerami w springu, i jeden z logiką aplikacji (i powinny być tak zrobione że w logice aplikacji nie ma nic odn. http, żadnych jsonów, kodów, statusów, etc.) - i do tego modułu nalezy normalnie napisać testy, nie wymagają nawet springa i mvc, tylko sam junit i asercje.
drugi, który jest samymi kontrolerami - i tu nie powinno być logiki związanej z domeną - a tylko logika związana z http - i tutaj powinny być testy też, ale powinno być ich mało, być krótkie i z reguły wystarczy jeden per endpoint. Tylko te testy powinny być napisane tak, żeby nie miały silnego przywiązania do springa najlepiej. Powinny wyglądać jakoś tak:

 @Test
 void testLoginEndpoint() {
   assertEndpointReturns("/login", emptyMap(), 422);
 }
 @Test
 void testLoginEndpointLogin() {
   assertEndpointReturns("/login", map("login", "user", "password", "123"), 200);
 }

i potem oczywiście te metody jakoś zaimplementować, również to może być mockMvc, albo inny sposób.

Nawiązując do tego drugiego modułu, który opisałeś, udało mi się wyskrobać coś takiego:

@Test
void shouldRespondWithOkStatusAndHaveCorrectFieldNamesOnSuccessfulEmailLogin() throws Exception {
    final var requestBody = "{\"usernameOrEmail\": \"[email protected]\",\"password\": \"testPassword@123\"}";

    assertEndpointReturns("/api/v1/auth/login",
            requestBody,
            200,
            "$.accessToken",
            "$.tokenType");
}

@Test
void shouldReturnUnauthorizedStatusForInvalidUsernameLoginWhenPasswordIsCorrect() throws Exception {
    final var requestBody = "{\"usernameOrEmail\": \"testInvalidUsername\",\"password\": \"testPassword@123\"}";

    assertEndpointReturns("/api/v1/auth/login",
            requestBody,
            401);
}

private void assertEndpointReturns(String endpoint, String requestBody, int expectedStatusCode,
                                   String... expectedJsonPaths) throws Exception {

    var result = mockMvc.perform(post(endpoint)
            .contentType(MediaType.APPLICATION_JSON)
            .content(requestBody))
            .andExpect(status().is(expectedStatusCode));

    for (String jsonPath : expectedJsonPaths) {
        result.andExpect(jsonPath(jsonPath).exists());
    }
}

O takie podejście mniej więcej Ci chodziło ?

0
Ornstein napisał(a):

Nawiązując do tego drugiego modułu, który opisałeś, udało mi się wyskrobać coś takiego:

[...]

O takie podejście mniej więcej Ci chodziło ?

No mniej więcej o coś takiego chodziło.

Aczkolwiek, nie jestem pewien czy asercja jsonPath().exists() jest konieczna tutaj. Ja bym ją chyba wywalił. Zamiast tego napisałbym inny test, specjalnie pod te pola (i ten test nie sprawdzałby już status code'a), i oczywiście nie sprawdzałby czy exists czy nie exists, tylko sprawdzałby faktyczną wartość. No bo weź pod uwagę, że żeby taki test z exists() przeszedł to wystarczy zwrócić pole z nullem albo "". A taki test raczej jest średni.

0

Nie powinien.

Zakładając, że ktoś szuka dziurawego endpointa w Twojej aplikacji w zautomatyzowany sposób tj. strzela po różnych endpointach, jeżeli zwracasz im jakich pol oczekujesz to gwarantuję Ci, że są tacy którzy to sobie zautomatyzują i następny request będzie szedł z poprawnymi polami.

Jeżeli ktoś celuje dokładnie w Twoją aplikację, to oczywiście może podejrzeć sobie jaki endpoint jest wywoływany.

1 użytkowników online, w tym zalogowanych: 0, gości: 1