Wpis z mikrobloga

Cześć,
bawię się trochę w refactor testów w celu zwiększenia ich czytelności. Miałbym dwa pytania, zobrazujmy sobie je na przykładzie.
Oto klasa którą testuję:

@Component
class CookieDeleterImpl implements CookieDeleter {
@OverRide
public void deleteCookie(HttpServletRequest request, HttpServletResponse response, String name) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
Arrays.stream(cookies)
.filter(cookie -> name.equals(cookie.getName()))
.forEach(cookie -> {
cookie.setValue(null);
cookie.setMaxAge(0);
cookie.setPath("/");
response.addCookie(cookie);
});
}
}
}

Klasa testowa:

public class CookieDeleterImplTest {
private CookieDeleterImpl deleter;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@BeforeEach
void setUp() {
deleter = new CookieDeleterImpl();
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
}
@test
void whenCookiePresent_Delete() {
addCookieToRequest("cookie", "value");

deleter.deleteCookie(request, response, "cookie");

assertCookieDeleted(response, "cookie");
}
private void addCookieToRequest(String name, String value) {
Cookie cookie = new Cookie(name, value);
request.setCookies(cookie);
}
private void assertCookieDeleted(MockHttpServletResponse response, String cookieName) {
Cookie[] cookies = response.getCookies();
assertNotNull(cookies);
assertTrue(Arrays.stream(cookies).anyMatch(cookie ->
cookieName.equals(cookie.getName()) &&
cookie.getMaxAge() == 0 &&
cookie.getValue() == null),
"Cookie should be set to deleted");
}
}

1. Czy test dla Was jest czytelny i zrozumiały ? Zastanawiam się, czy zbyt dużo szczegółów nie przeniosłem do metod pomocniczych.
2. Czy metody pomocnicze powinienem przenieś do oddzielnej klasy, czy też w sytuacji, gdzie jest mało testów, mogą zostać w klasie testowej?
#java #spring #naukaprogramowania
  • 18
  • Odpowiedz
@Kamishimi: kod nie jest dla mnie ani czytelny ani zrozumiały.
Pytania, które mi się nasuwają:
1. O co w ogóle chodzi? Usuwanie ciasteczek? To ma być klient czy serwer http? Klient może go po prostu nie wysłać, serwer nie ustawiać, więc o co kaman?
2. Usuwanie ciasteczek? To co oznacza "Cookie should be set to deleted"?

Also, fatalny angielski w tej [:-3] linijce.
  • Odpowiedz
  • 0
@Bordomir:
Proces usuwania ciasteczek odnosi się do serwera HTTP.
W projekcie wykorzystuję JWT dla autentykacji użytkowników. W momencie logowania, tworzę ciasteczko zawierające ten token, które jest następnie wykorzystywane do autentykacji. Gdy użytkownik decyduje się na wylogowanie, chcę wysłać polecenie do przeglądarki klienta, aby usunęła to ciasteczko z tokenem.

Ten kontroler odpowiada za to:

@Slf4j
@RestController
@RequestMapping("/api/v1/users")
public class LogoutController {
private final CookieDeleter cookieDeleter;
private final ApplicationMessageService messageService;
public LogoutController(CookieDeleter
  • Odpowiedz
@Kamishimi: uwierzytelnienia*, autentykacja to kalka językowa i błąd.

Web serwer nie ma możliwości wysyłania żadnych poleceń do przeglądarki - takie rzeczy załatwia się w nagłówkach protokołu HTTP. Nie lepiej uczyć się na realnych przykładach?
  • Odpowiedz
  • 0
@Bordomir: Może trochę źle napisałem. W mojej implementacji, proces usuwania ciasteczka realizowany jest przez ustawienie nagłówka Set-Cookie w odpowiedzi HTTP. Kiedy użytkownik decyduje się na wylogowanie, serwer wysyła odpowiedź z ciasteczkiem, w którym Max-Age jest ustawione na 0, a wartość ciasteczka na null.

Czy to jest złe podejście ?
  • Odpowiedz
@Kamishimi: odpisywałem tylko w odniesieniu do Twojego fragmentu kodu - że on jest dla mnie nie zrozumiały. Jak mówisz, że chodzi o symulację wylogowania, to rozumiem znacznie więcej.

Oczywiście masz rację z tym, że jakaś część przeglądarek zareaguje na to co ustawiłeś i w zależności od konfiguracji - usunie ciasteczko po stronie klienta.

Teraz nie rozumiem tylko jednego - dlaczego miałbyś się upewniać, że ciasteczko istnieje? Da się wylogować nie mając
  • Odpowiedz
Ja bym tych assertów nie pchał do osobnej metody tylko sprawdzał to w faktycznej metodzie testowej, i jeden po drugim, a nie tak:

assertTrue(Arrays.stream(cookies).anyMatch(cookie ->
cookieName.equals(cookie.getName()) &&
cookie.getMaxAge() == 0 &&
cookie.getValue() == null),
"Cookie should be set to deleted");

bo to jest zupełnie niezrozumiały na pierwszy rzut oka krzaczek, a testy powinny być przejrzyste i powinieneś wiedzieć która linia nie przechodzi i dlaczego nie przechodzi. Co do funkcji pomocniczych w testach,
  • Odpowiedz
  • 0
@Bordomir: To zmienię żeby tylko osoby zalogowane miały do niego dostęp. A ta symulacja w teście jest ok ?

@test
void whenCookiePresent_Delete() {
Cookie cookie = new Cookie("cookie", "value");
request.setCookies(cookie);

deleter.deleteCookie(request, response, "cookie");

Optional<Cookie> optionalCookie = Arrays.stream(response.getCookies())
.filter(c -> "cookie".equals(c.getName()))
.findFirst();

optionalCookie.ifPresent(deletedCookie -> {

assertThat(deletedCookie.getName()).isEqualTo("cookie");

assertThat(deletedCookie.getMaxAge()).isEqualTo(0);

assertThat(deletedCookie.getValue()).isNull();
});
}
  • Odpowiedz
  • 0
@bart1234: tak lepiej ?

@test
void whenCookiePresent_Delete() {
Cookie cookie = new Cookie("cookie", "value");
request.setCookies(cookie);

deleter.deleteCookie(request, response, "cookie");

Optional<Cookie> optionalCookie = Arrays.stream(response.getCookies())
.filter(c -> "cookie".equals(c.getName()))
.findFirst();

optionalCookie.ifPresent(deletedCookie -> {

assertThat(deletedCookie.getName()).isEqualTo("cookie");

assertThat(deletedCookie.getMaxAge()).isEqualTo(0);

assertThat(deletedCookie.getValue()).isNull();
});
}
  • Odpowiedz
@Kamishimi: wydaje się dużo lepiej imo. możesz jeszcze dodać verify(response, times(1)).addCookie(any(Cookie.class)); oraz assertThat(optionalCookie).isPresent(); przed assertami optionala.
  • Odpowiedz