Adnotacja @Transactional w testach

Adnotacja @Transactional w testach
SC
  • Rejestracja:ponad rok
  • Ostatnio:3 dni
  • Postów:19
0

Czy w testach powinno używać się adnotacji @Transactional? Musiałem ją dodać w tym teście, bo inaczej w metodzie findEventToEdit, konkretnie w linii if (!currentUser.equals(event.getOrganizer())) { leciał LazyInitializationException. Encje Event i AppUser są w relacji jednokierunkowej OneToOne. Teraz odkąd zacząłem używać w testach 2 plików sql - pierwszy do dodawania danych testowych przed każdym testem, drugi do czyszczenia tabel po każdym teście to mam nowy problem - metoda getReferenceById zwraca mi obiekt AppUser$HibernateProxy... zamiast AppUser, w którym wszystkie pola są null. Usunięcie adnotacji @Transactional z testu pomaga, ale wtedy leci wcześniej wspomniany wyjątek. W czym jest problem?

Kopiuj
@Test
@Transactional
void shouldReturnEventToEdit() {
    // when
    var eventToEdit = organizerEventService.findEventToEdit(appUserRepository.getReferenceById(2L), 5L);
    // then
    assertThat(eventToEdit.getName()).isEqualTo("Java Dev Talks #5");
    assertThat(eventToEdit.getEventImageData()).isNotBlank();
    assertThat(eventToEdit.getEventImage()).isNull();
    assertThat(eventToEdit.getEventType()).isEqualTo(MEETING);
    assertThat(eventToEdit.getDateTime()).isEqualTo(TOMORROW.plusMonths(1).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
    assertThat(eventToEdit.getLanguage()).isEqualTo("polski");
    assertThat(eventToEdit.getAdmission()).isEqualTo(FREE);
    assertThat(eventToEdit.getCity()).isEqualTo("Rzeszów");
    assertThat(eventToEdit.getLocation()).isEqualTo("WSIiZ");
    assertThat(eventToEdit.getAddress()).isEqualTo("Sucharskiego 2, 35-225 Rzeszów");
    assertThat(eventToEdit.getDescription()).isEqualTo("Spotkanie rzeszowskiej grupy pasjonatów języka Java.");
}
Kopiuj
@Transactional
public EventEditDTO findEventToEdit(AppUser currentUser, Long id) {
    Optional<Event> eventOpt = eventRepository.findById(id);
    if (eventOpt.isPresent()) {
        Event event = eventOpt.get();
        if (!currentUser.equals(event.getOrganizer())) {
            throw new AccessDeniedException("Access is denied");
        }

        return EventEditDTOMapper.mapToEventEditDTO(event);
    }

    throw new EventNotFoundException("Event with ID " + id + " not found");
}
edytowany 1x, ostatnio: Scarilt
RequiredNickname
poczytaj o tym jak używać optionala. Podpowiedź: na pewno nie tak jak nullchecka w ifie...
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:około 7 godzin
  • Lokalizacja:Koszalin
  • Postów:10094
1

Nie ważne jest czy "się powinno" czy "nie powinno". Istotne jest jakie to ma konsekwencje.

Zalety użycia @Transactional w teście:

  • Na ten moment testy się odpalają

Wady:

  • Przywiązanie do frameworka
  • Słaby design testów, bo taką adnotacją trudniej jest kontrolować w testach co się dzieje - trudno jest z czymś takim łatwo wydzielić np helper.
  • Nie można odpalić testów bez springa (i np wstrzyknąć fejkowej implementacji)

Rozważ wady i zalety, i sam zdecyduj co jest dla Ciebie lepszym wyjściem.

Black007
  • Rejestracja:ponad 21 lat
  • Ostatnio:3 dni
0

Możesz dodać do Encji: @OneToOne(fetch = FetchType.EAGER). I chyba wtedy możesz wywalić @Trasactonal z testu, ale teraz z głowy piszę
Natomiast zastanowiłbym się nad tym, czy zamiast referencji nie lepiej trzymać Id tego drugie obiektu


"Nie popełnia błędów tylko ten, kto nic nie robi"
edytowany 1x, ostatnio: Black007
MI
  • Rejestracja:około 9 lat
  • Ostatnio:około 7 godzin
  • Postów:110
0
Black007 napisał(a):

Możesz dodać do Encji: @OneToOne(fetch = FetchType.EAGER). I chyba wtedy możesz wywalić @Trasactonal z testu, ale teraz z głowy piszę
Natomiast zastanowiłbym się nad tym, czy zamiast referencji nie lepiej trzymać Id tego drugie obiektu

i wtedy w teście, możesz uderzyć to im id'ku za pomocą repo (ja raczej korzystam w entityManagera) do encji do której chcesz się dostać. Wtedy @Transactional w teście nie będzie potrzebny.

SC
  • Rejestracja:ponad rok
  • Ostatnio:3 dni
  • Postów:19
0

@Black007 adnotacja @OneToOne ma domyślnie ustawione FetchType.EAGER.

@Black007 @MateInf chodzi o to żeby wgl. usunąć relację OneToOne i trzymać tylko id obiektu?

edytowany 2x, ostatnio: Scarilt
MI
  • Rejestracja:około 9 lat
  • Ostatnio:około 7 godzin
  • Postów:110
2
Scarilt napisał(a):

@Black007 @MateInf chodzi o to żeby wgl. usunąć relację OneToOne i trzymać tylko id obiektu?

to miałem na myśli

SC
  • Rejestracja:ponad rok
  • Ostatnio:3 dni
  • Postów:19
0

@MateInf mam jeszcze jeden problem - odkąd zacząłem dodawać dane testowe przez plik sql to nie przechodzą mi testy, które zapisują coś w bazie. W tym pliku dodaję np. 15 użytkowników z id 1, 2, 3 itd., z góry określam ich id, bo muszę wiedzieć w testach co wyciągnąć i później w teście, który ma utworzyć nowego użytkownika dostaję błąd związany z naruszeniem ograniczenia klucza głównego lub indeksu unikalnego. W każdej encji mam adnotację @GeneratedValue(strategy = GenerationType.IDENTITY). Wcześniej dodawałem dane testowe przez repozytoria Spring Data JPA i było dobrze. W czym tu jest problem?

MA
Nie wystarczy usunąć, trzeba jeszcze dać restart identity i zobacz co robi metoda getReferenceById. Pozderki, można zamykać
SC
@Majksu: ALTER TABLE app_user ALTER COLUMN id RESTART WITH 1; takim poleceniem? Zrobiłem coś takiego dla wszystkich tabel i nie pomogło.
MI
  • Rejestracja:około 9 lat
  • Ostatnio:około 7 godzin
  • Postów:110
0
Scarilt napisał(a):

@MateInf mam jeszcze jeden problem - odkąd zacząłem dodawać dane testowe przez plik sql to nie przechodzą mi testy, które zapisują coś w bazie. W tym pliku dodaję np. 15 użytkowników z id 1, 2, 3 itd., z góry określam ich id, bo muszę wiedzieć w testach co wyciągnąć i później w teście, który ma utworzyć nowego użytkownika dostaję błąd związany z naruszeniem ograniczenia klucza głównego lub indeksu unikalnego. W każdej encji mam adnotację @GeneratedValue(strategy = GenerationType.IDENTITY). Wcześniej dodawałem dane testowe przez repozytoria Spring Data JPA i było dobrze. W czym tu jest problem?

Nie wiem, stackOverFlow albo chat gpt pewnie wie. Natomiast, po co ustawiasz z góry idiki? Logicznie myśląc, ze wszystkich pól które może jakkolwiek być najbardziej "nadzorowane" przez omry (pewnie hibernate) - IDki są zdecydowanie tymi polami. Po co więc ingerować swoimi działaniami jak się nie zna świetnie bebechów co się tam dzieje. Nie możesz np. ustawić sobie unikatowe nazwy userów i w teście po tych userach wyciągać rekordy z bazy do testu ?

edytowany 1x, ostatnio: MateInf
PI
  • Rejestracja:ponad 9 lat
  • Ostatnio:4 miesiące
  • Postów:2787
0

Tak to jest jak metody serwisowe zwracają encje JPA zamiast zmapowane DTOsy :P

Black007
DTO to raczej warstwa prezentacji (np. Rest Controller), serwisy w ujęciu "springowym" mają za zadanie właśnie zarządzać sesją
PI
Nope. Owszem zarządzać sesją, ale jak zwracają encje JPA zamiast zmapowane DTOsy, to się kończy jak w niniejszym wątku.
Black007
Yes. autorowi wątku wyciekła jedna warstwa do drugiej, jakby nie używać tego repo, to nie byłoby problemu
Black007
@Pinek: po co ci DTO w warstwie serwisów? Sama nazwa Data Tranfer Object sugeruje ze obiekt jest uzywany do transferu danych. Fasada może zwracać dto. A też nie musi, bo można wywołania jej metod "ubrac" w adaptery
PI
Po to, żeby np właśnie nie mieć tysiąca dziwnych błędów z transakcyjnością. Poza tym odkąd takie DTOsy mam w serwisach, żyje mi się o wiele łatwiej. No i znajomi programiści też tak robią.
KamilAdam
  • Rejestracja:ponad 6 lat
  • Ostatnio:około miesiąc
  • Lokalizacja:Silesia/Marki
  • Postów:5505
1

Jak tak oftopujemy to:

Tak to jest jak metody serwisowe zwracają encje JPA zamiast zmapowane DTOsy 😛

DTO to raczej warstwa prezentacji (np. Rest Controller), serwisy w ujęciu "springowym" mają za zadanie właśnie zarządzać sesją — Black007 dziś, 07:09

Nope. Owszem zarządzać sesją, ale jak zwracają encje JPA zamiast zmapowane DTOsy, to się kończy jak w niniejszym wątku. — Pinek 1 minuta temu

Pracuję już 12 lat i widziałem chyba wszystkie możliwe rozwiązania

  1. encja na twarz i pchasz
  2. encje w serwisach,w kontrolerach dto
  3. encje nie wychodzą z repo
  4. w ogóle nie używamy hibernate/JPA

Przyznam szczerze iż im wyższy liczba tym bardziej podoba mi sie rozwiązanie


Mama called me disappointment, Papa called me fat
Każdego eksperta można zastąpić backendowcem który ma się douczyć po godzinach. Tak zostałem ekspertem AI, Neo4j i Nest.js . Przez mianowanie
PI
encje nie wychodzą z repo z repo czy z serwisów?
PI
Czyli repo w sobie robi już mapowanie jakieś?
KamilAdam
Tak, repo robi dwie rzeczy. Użuwa JPA i mapuje
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 6 godzin
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4708
2
Pinek napisał(a):

Tak to jest jak metody serwisowe zwracają encje JPA zamiast zmapowane DTOsy :P

DTOsy rozwiązują część problemów i generują zupełnie nowe, jeszcze bardziej porypane:
mappery, detached entities.
Szczególnie zapis jakiegoś złożonego obiektu do bazy bywa zabawny. W jednym korpo architekci wymyślili, że będą różne DTOsy (ETO, DTO,CTO :-)) - każdy na kolejnym poziomie. Z samych maperów można było zrobić doktorat.


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 1x, ostatnio: jarekr000000
PI
  • Rejestracja:ponad 9 lat
  • Ostatnio:4 miesiące
  • Postów:2787
0
jarekr000000 napisał(a):
Pinek napisał(a):

Tak to jest jak metody serwisowe zwracają encje JPA zamiast zmapowane DTOsy :P

DTOsy rozwiązują część problemów i generują zupełnie nowe, jeszcze bardziej porypane:
mappery, detached entities.
Szczególnie zapis jakiegoś złożonego obiektu do bazy bywa zabawny. W jednym korpo architekci wymyślili, że będą różne DTOsy (ETO, DTO,CTO :-)) - każdy na kolejnym poziomie. Z samych maperów można było zrobić doktorat.

No nie wiem - w obecnym projekcie serwisy zwracają DTOsy i czuję się w tym o wiele lepiej niż w wielu poprzednich projektach, gdzie zwracane były encje JPA.

SC
  • Rejestracja:ponad rok
  • Ostatnio:3 dni
  • Postów:19
0

@MateInf użytkownicy mają unikatowe nazwy, tzn. email jest unikatowy, ale mam endpointy i metody w serwisach, które wyciągają użytkownika po id i żeby napisać test dla takiej metody muszę wiedzieć jakie id ma konkretny użytkownik.

lambdadziara
testuj findById w jednym test case, a saveUser w innym test case i uzywaj @BeforeEach do usuwania danych/inserta z plikow pomiedzy kazdym test casem
SC
@lambdadziara: testuję tak, tylko czasami do testu potrzebuję obiekt użytkownika i stąd to użycie metody getReferenceById.
Black007
  • Rejestracja:ponad 21 lat
  • Ostatnio:3 dni
0

Jak już wróciliśmy z krainy opfftopa...
Mam 18 lat doświadczenia 😛
I widzę że:
@Scarilt warstwy Ci wyciekają. Ten test nie może nie być Transactional, bo jawnie używasz repository!
Pewnie w teście potrzebujesz wartości, która jest zwracana przez to repo.
Zamiast repo z wywołaną metodą wrzuć tą wartość (która jest zwracana przez repo) i wywal Transactional.


"Nie popełnia błędów tylko ten, kto nic nie robi"
MA
Chcesz powiedzieć, że nie może użyć repo żeby sobie wyciągnąć cokolwiek by chciał w teście, skoro ma to już w bazie? Pomijając design, to w czym problem?
Black007
  • Rejestracja:ponad 21 lat
  • Ostatnio:3 dni
0
Kopiuj
@Test
void shouldReturnEventToEdit() {
    // when
    var givenAppUser = new AppUser();
    var eventToEdit = organizerEventService.findEventToEdit(givenAppUser, 5L);
    // then
    assertThat(eventToEdit.getName()).isEqualTo("Java Dev Talks #5");
    assertThat(eventToEdit.getEventImageData()).isNotBlank();
    assertThat(eventToEdit.getEventImage()).isNull();
    assertThat(eventToEdit.getEventType()).isEqualTo(MEETING);
    assertThat(eventToEdit.getDateTime()).isEqualTo(TOMORROW.plusMonths(1).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
    assertThat(eventToEdit.getLanguage()).isEqualTo("polski");
    assertThat(eventToEdit.getAdmission()).isEqualTo(FREE);
    assertThat(eventToEdit.getCity()).isEqualTo("Rzeszów");
    assertThat(eventToEdit.getLocation()).isEqualTo("WSIiZ");
    assertThat(eventToEdit.getAddress()).isEqualTo("Sucharskiego 2, 35-225 Rzeszów");
    assertThat(eventToEdit.getDescription()).isEqualTo("Spotkanie rzeszowskiej grupy pasjonatów języka Java.");
}

Coś w ten deseń


"Nie popełnia błędów tylko ten, kto nic nie robi"
edytowany 1x, ostatnio: Black007
SC
  • Rejestracja:ponad rok
  • Ostatnio:3 dni
  • Postów:19
1

@Black007 jeśli chodzi o ten problem z pierwszego posta to mam jakieś tipy jak to naprawić, ale na razie muszę poprawnie dodać dane testowe. Pisałem wyżej o tym.

Black007
wybacz, tak mocno zafixowałem się na tym offtopie że pominąłem :D
Black007
  • Rejestracja:ponad 21 lat
  • Ostatnio:3 dni
0

@Majksu
Chodzi o to, że zwykle w Springu masz tak: JpaRepo jest używane (czyli wstrzyknięte) przez service. Service ma jakąś metodę, która używa tego repo, np. dostałem na wejściu imię i nazwisko i chce to zapisać, Robię sobie new Person i ustawiam tam te dane i zapisuje. Ta metoda jest oznaczona @transactional, czyli aop springa tworzy transakcje, a po wyjściu ją commituje (skrót myślowy tutaj). I teraz mam "Klineta" tego serwisu, np. Fasadę lub controller restowy. I ta fasada, czy controller nic nie wiedzą o czymś takim jak sesja bazodanowa, to też same nie muszą mieć transactional.
Teraz pytanie, czy majac taki podział, to czy chcę testować zapis do bazy? Powinien testować funkcjonalność, a test czy zapisuje się do bazy, tak naprawdę testuje czy baza danych działa.


"Nie popełnia błędów tylko ten, kto nic nie robi"
SC
  • Rejestracja:ponad rok
  • Ostatnio:3 dni
  • Postów:19
0

@Black007 jaką funkcjonalność w ten sposób przetestujesz? Ja myślę, że dla kontrolerów powinniśmy pisać testy integracyjne (tutaj różne osoby używają różnych nazw na ten typ testów), w których np. za pomocą RestTemplate strzelisz na endpoint i przelecisz od kontrolera do samej bazy. A Ty chciałbyś mockować odpowiedzi z metod repozytoriów, czy nawet z metod serwisów? W kontrolerach chyba nie masz skomplikowanej logiki, tylko raczej wywołanie jakiejś metody z serwisu i ewentualnie opakowanie zwróconego obiektu w obiekt typu ResponseEntity.

Black007
  • Rejestracja:ponad 21 lat
  • Ostatnio:3 dni
1

W kontrolerach chyba nie masz skomplikowanej logiki, tylko raczej wywołanie jakiejś metody z serwisu i ewentualnie opakowanie zwróconego obiektu w obiekt typu ResponseEntity.

Dokładnie tak. Controlery tylko wywołują fasade.
Dlatego testy integracyjne mogą być na poziomie fasady. Czyli tego co ma się tam dziać, logiki biznesowej.
Można użyć testów czarnej skrzynki.
Wrzucasz parametry i sprawdzasz oczekiwaną wartość.
Albo białej skrzynki. Mockujesz np. Repository i sprawdzasz czy dany mock był wywołany np. 1 raz z danymi parametrami.


"Nie popełnia błędów tylko ten, kto nic nie robi"

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.