Podmiana implementacji typu implementującego interfejs

Odpowiedz Nowy wątek
2020-02-15 19:13

Rejestracja: 3 lata temu

Ostatnio: 17 godzin temu

0

Mamy standardowy serwis i repozytorium

@Service
class TestService {
    private final TestRepository testRepository;

    @Autowired
    TestService(TestRepository testRepository) {
        this.testRepository = testRepository;
    }
    //...
}

@Repository
interface TestRepository extends CrudRepository<TestEntity, Long> {
}

@Entity
class TestEntity {
    @Id
    private Long id;
    //...
}

Chcę teraz podmienić implementację TestRepository na własną (np. na potrzeby testów). Mam coś takiego:

class InMemoryCrudRepository<T, ID> implements CrudRepository<T, ID> {

    private final Map<ID, T> repository = new HashMap<>();

    @Override
    public <S extends T> S save(S s) {
        return s;
    }
    //...
}

Chodzi o to, żeby "podstawić" InMemoryCrudRepository pod TestRepository.

class TestServiceTest {

    private final TestService testService;

    TestServiceTest() {
        testService = new TestService(new InMemoryCrudRepository<TestEntity, Long>()); //to się oczywiście nie skompiluje
    }
}

Do konstruktora muszę przekazać coś, co:

  1. Pochodzi od TestRepository
  2. Rozszerza InMemoryCrudRepository
    Mój pomysł:
    class InMemoryTestRepository<T, ID> extends InMemoryCrudRepository<T, ID> implements TestRepository {
    }

Niestety dostaję błąd:

Error:(3, 1) java: org.springframework.data.repository.CrudRepository cannot be inherited with different arguments: <com.example.demo.TestEntity,java.lang.Long> and <T,ID>

Rozwiązaniem jest sparametryzowanie TestRepository

@Repository
interface TestRepository<X, Y> extends CrudRepository<X, Y> {
}

ale wtedy w serwisie musiałbym podać typy, żeby dało się tego normalnie używać.

@Service
class TestService {
    private final TestRepository<TestEntity, Long> testRepository;
    //było private final TestRepository testRepository;
}

A tego bym nie chciał. Jest jakiś inny sposób, żeby w serwisie podmienić implementację TestRepository? (o ile to możliwe)

edytowany 2x, ostatnio: Potat0x, 2020-02-15 19:17

Pozostało 580 znaków

2020-02-15 19:19

Rejestracja: 6 lat temu

Ostatnio: 3 dni temu

Lokalizacja: Warszawa

1

1, Tworzyc konfiguracje recznie

  1. Zmusic springa zeby uzyl odpowiedniego beana dla odpowiedniego profilu lub jego braku
  2. Order / Primary + definicja beana testowego w test scope

Pozostało 580 znaków

2020-02-15 19:21

Rejestracja: 12 lat temu

Ostatnio: 4 minuty temu

1

Ale dlaczego to InMemoryRepository ma generyki? Zrób dedykowane i będzie w porządku, chyba że tworzysz jakiś framework :)


IT menedżer

Pozostało 580 znaków

2020-02-15 19:28

Rejestracja: 2 lata temu

Ostatnio: 35 sekund temu

Lokalizacja: Kraków

0

Jesteś pewien, że potrzebujesz tej implementacji InMemory? Rozumiem, że to ma być jakaś implementacja do testów odcięta od bazy, nie lepiej by Ci było w testach podpinać się do jakiejś faktycznej bazy in memory (np. H2) albo setupować sobie test containers?


Nie znam się, ale się wypowiem
edytowany 1x, ostatnio: superdurszlak, 2020-02-15 19:28
Jeśliby miał startować z H2, to równie dobrze może postawić rzeczywistą bazę - przynajmniej testy będą rzetelne :-P - Patryk27 2020-02-15 19:49
@Patryk27: nie będę nawet próbował bronić podejścia z H2, pałam do H2 szczerą niechęcią ale zawsze to jakieś rozwiązanie :D - superdurszlak 2020-02-15 20:02

Pozostało 580 znaków

2020-02-15 19:43

Rejestracja: 3 lata temu

Ostatnio: 17 godzin temu

0

@artur52
Mógłbyś trochę bardziej objaśnić (kompletnie nie czaję :P)

@Charles_Ray
Dlatego, żeby InMemoryRepository mogło być użyte do zastąpienia wielu różnych repozytoriów.

@superdurszlak

Jesteś pewien, że potrzebujesz tej implementacji InMemory? Rozumiem, że to ma być jakaś implementacja do testów odcięta od bazy

Tak. Chodzi o to, żeby nie stawiać kontekstu Springa (chociaż mi to jakoś bardzo nie przeszkadza), więc H2 i testcontainers odpadają. (BTW testcontainers to genialna sprawa, chociaż u mnie na kompie za cholerę nie może się połączyć z Dockerem :|)

Chciałbym jakoś zastąpić Mockito czymś "normalnym", ale rozwiązanie musi być maksymalnie proste, żeby zwiększyć szanse jego przeforsowania.

edytowany 1x, ostatnio: Potat0x, 2020-02-15 19:44

Pozostało 580 znaków

2020-02-15 20:01

Rejestracja: 2 lata temu

Ostatnio: 35 sekund temu

Lokalizacja: Kraków

Potat0x napisał(a):

Tak. Chodzi o to, żeby nie stawiać kontekstu Springa (chociaż mi to jakoś bardzo nie przeszkadza), więc H2 i testcontainers odpadają. (BTW testcontainers to genialna sprawa, chociaż u mnie na kompie za cholerę nie może się połączyć z Dockerem :|)

Chciałbym jakoś zastąpić Mockito czymś "normalnym", ale rozwiązanie musi być maksymalnie proste, żeby zwiększyć szanse jego przeforsowania.

A, ok, czyli po prostu chcesz poskładać sobie w kupę te komponenty bez Springa i wstrzyknąć zachowanie żeby ominąć Mockito-izację :D

No to w sumie możesz

  • złożyć ręką (chyba nie masz aż tylu dependencji żeby to był jakiś wielki problem, sądząc po PoC)
  • złożyć jakimś innym toolem do dependency injection żeby nie używać Springa - tylko po co robić sobie pod górkę, wg. mnie byłoby bez sensu
  • skonfigurować testy tak, żeby nie ładowały całego kontekstu aplikacji a jedynie potrzebne komponenty

A co do tej testowej implementacji, klasa implementująca i tak nie będzie generyczna, skoro implementuje skonkretyzowany interfejs TestRepository, takie coś Cię nie urządza?

class InMemoryTestRepository extends InMemoryCrudRepository<TestEntity, Long> implements TestRepository {
}

Nie znam się, ale się wypowiem

Pozostało 580 znaków

2020-02-15 20:43

Rejestracja: 3 lata temu

Ostatnio: 17 godzin temu

0

Dokładnie o to chodziło, mój mózg chyba nie pracuje :D
złożyć ręką (chyba nie masz aż tylu dependencji żeby to był jakiś wielki problem, sądząc po PoC) - w sumie to teraz nie pamiętam, ale myślę że kilka przypadków da ze zdemockitować, albo przynajmniej inaczej pisać nowe testy. W używaniu Mockito męczy mnie, że

  • muszę analizować bebechy testowanych klas żeby sprawdzić jakich funkcji z repozytoriów używają (to jest chore)
  • zazwyczaj jest testowana tylko część czytająca (co przy okazji zaniża pokrycie kodu)
  • nie da się sprawdzić, co zostało zapisane do repozytorium podczas testów (to jest moja główna motywacja)

Naprawdę nie dziwię się, że ludziom nie chce się pisać testów, jeżeli tak to wygląda.

Osobiście użyłbym po prostu Testcontainers, bez rozwodzenia się nad podziałem na testy jednostkowe/integracyjne/blabla, ale jak to zazwyczaj wygląda w przypadku serwerów CI? Zdarzają się problemy?

nie będę nawet próbował bronić podejścia z H2, pałam do H2 szczerą niechęcią

Masz jakieś "ciekawe" doświadczenia z tym związane?

edytowany 1x, ostatnio: Potat0x, 2020-02-15 20:44

Pozostało 580 znaków

2020-02-15 21:07

Rejestracja: 6 lat temu

Ostatnio: 3 dni temu

Lokalizacja: Warszawa

1

@Potat0x:

  1. spójrz na to: LINK:
  2. @Profile i profil startowy aplikacji.
  3. Przykryj pustym interfejsem. W pakietach testowych zrób sobie impl. tej klasy i wrzuć @Primary
edytowany 2x, ostatnio: artur52, 2020-02-15 21:27
Link do YT przenosi do wątku po kliknięciu :D - superdurszlak 2020-02-15 21:23

Pozostało 580 znaków

2020-02-15 21:12

Rejestracja: 2 lata temu

Ostatnio: 35 sekund temu

Lokalizacja: Kraków

1
Potat0x napisał(a):
  • nie da się sprawdzić, co zostało zapisane do repozytorium podczas testów (to jest moja główna motywacja)

Zależy co rozumiesz przez co zostało zapisane do repozytorium - to co zostało wrzucone do repozytorium jako entitka czy to co repo robi z tym pod spodem? Jak to drugie no to taką in-memory implementacją i tak tego nie sprawdzisz, bo zastępujesz to swoją atrapą :P

Jeśli chcesz podejrzeć, co się działo z repo, to jak najbardziej możesz, Mockito nie kończy się na mock i when. Do tego są jeszcze poboczne paczki, część w samym Mockito, część bodajże z hamcrest, samego JUnit i co to tam jeszcze jest. Wybacz, ale nie pamiętam w tej chwili co jest skąd, nie mam do tego głowy

  • spy - zamiast pustej wydmuszki dostajesz w pełni funkcjonalny obiekt, na którym dodatkowo możesz dokładać np. jakieś weryfikacje i takie tam. Jak nazwa wskazuje, podglądać go ;)
  • ArgumentMatchers - do sprawdzania, czy argumenty wywołań pasują do jakichś kryteriów, mniej lub bardziej restrykcyjnych
  • verify - do sprawdzania wywołań (można też robić asercje, że coś jest wywołane maksymalnie 2 razy albo wcale itd)
  • ArgumentCaptors czy jakoś tak - możesz przechwycić argumenty wywołań, wyciągnąć je i coś tam z nimi zrobić
  • InOrder - podobnie jak verify, ale dodatkowo możesz np. weryfikować kolejność wywołań

Naprawdę nie dziwię się, że ludziom nie chce się pisać testów, jeżeli tak to wygląda.

Osobiście użyłbym po prostu Testcontainers, bez rozwodzenia się nad podziałem na testy jednostkowe/integracyjne/blabla, ale jak to zazwyczaj wygląda w przypadku serwerów CI? Zdarzają się problemy?

Musiałbym zapytać kolegów z zespołu obok, jakie mieli doświadczenia z test containers, sam jestem skazany na używanie H2. Generalnie idea brzmi dużo rozsądniej od podkładania 'atrapy' bazy. Na dobrą sprawę brzmi też lepiej od robienia takiego własnego 'in memory', bo testując bardziej integracyjnie / e2e możesz chcieć uwzględnić (a możesz nawet nie wiedzieć, że chciałbyś) interakcje z prawdziwą bazą, szczególnie w jakichś bardziej rozbudowanych scenariuszach. Na przykład możesz mieć nie jeden data source, a kilka (i np. routować do jakichś read-only DS dla niektórych transakcji), a do czegoś takiego musiałbyś już stawiać taką atrapę, że operacja Fortitude wymięka ;)

nie będę nawet próbował bronić podejścia z H2, pałam do H2 szczerą niechęcią

Masz jakieś "ciekawe" doświadczenia z tym związane?

Cały szereg drobnych acz nieszczęśliwych doświadczeń, przede wszystkim z native queries i wykorzystywaniem ficzerów danego DBMS*. I występowaniem różnych drobnych rozbieżności między stanem rzeczywistym, a tym, który H2 uznaje za rzeczywisty i 'kompatybilny'.

* inb4 "nie używaj native queries" - czasami potrzebujesz czegoś, co ma SQL danego DBMS a JPQL / HQL już nie, czasami używasz czegoś innego niż Hibernate albo ma jakiś własny customowy DSL / QL, albo używa native queries


Nie znam się, ale się wypowiem
Test Containers dają radę :) ale to już nie testy w pamięci - Charles_Ray 2020-02-15 21:32
OP chyba liczył na bogatszą relację :D - superdurszlak 2020-02-15 21:33

Pozostało 580 znaków

2020-02-15 21:41

Rejestracja: 3 lata temu

Ostatnio: 3 tygodnie temu

0

Gdy bawiłem się w architekture heksagonalną to robiłem coś takiego

@Configuration
public class MyConfiguration {

    @Bean
    MyService myService(MyRepository myRepository) {
        return new MyService(myRepository);
    }

    MyService myService() {
        return new MyService(new InMemoryMyRepository());
    }
}

Serwis zostawał bez adnotacji @Service, gdy trzeba było go wstrzyknąć wykorzysytwany była metoda z adnotacją @Bean, gdy potrzebowałem go dla testów to tworzyłem go ręcznie przez drugą metodę.

Innym sposobem jest też używanie @ActiveProfile przy testach i dodawanie odpowiednich @Profile do repozytoriów.

edytowany 1x, ostatnio: Emdzej93, 2020-02-15 21:42

Pozostało 580 znaków

2020-02-15 22:43

Rejestracja: 8 miesięcy temu

Ostatnio: 1 miesiąc temu

0

W Springu jest jeszcze taka fajna adnotacja jak @MappedSuperClass, poczytaj sobie o niej

Ten komentarz nominuje do bzdury tygodnia. - Shalom 2020-02-15 23:44

Pozostało 580 znaków

Odpowiedz

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