Problem z mockiem w tescie jednostkowym

0

Mam problem mockując repozytorium partHistoryRepository. wykonujac metode findRestrictedIdentifiers zawsze zwraca mi nulla, podejrzewam, że to wina sposobu w jaki przekazuje parametry. Ale kompletnie nie widze tu błędu.

public FindResultDto<VehicleDeliveryNotePartHistoryDto> getVehicleDeliveryNoteParts(SearchDto searchDto, VehicleDeliveryNotesCriteriaDto criteriaDto) {
      PageRequest pageRequest = PageRequest.of(searchDto.getPage(), searchDto.getLimit().intValue());

      Page<BigInteger> page;
      String vins = CollectionUtils.isNullOrEmpty(criteriaDto.getVins()) ? null : String.join(",", criteriaDto.getVins());

      if (Boolean.TRUE.equals(criteriaDto.getRestrictedParts())) {
          page = partHistoryRepository.findRestrictedIdentifiers(vins, criteriaDto.getBuyerName(), criteriaDto.getStartDate(), criteriaDto.getEndDate(), SettingsService.getRestrictedPartDays(), pageRequest);
      } else {
          page = partHistoryRepository.findNotRestrictedIdentifiers(vins, criteriaDto.getBuyerName(), criteriaDto.getStartDate(), criteriaDto.getEndDate(), SettingsService.getRestrictedPartDays(), pageRequest);
      }

      List<Long> identifiers = page.getContent().stream()
              .map(BigInteger::longValue)
              .toList();

      List<VehicleDeliveryNotePartHistoryDto> partsHistory = partHistoryRepository.findAllByPartIdentifierIn(identifiers).stream()
              .map(partHistoryMapper::mapEntityToDto)
              .filter(p -> criteriaDto.getVdi4081Code() == null || p.getVdi4081Code().equals(criteriaDto.getVdi4081Code()))
              .collect(Collectors.groupingBy(VehicleDeliveryNotePartHistoryDto::getPartIdentifier))
              .entrySet().stream()
              .map(this::collectHistoryData)
              .toList();

      return FindResultDto.<VehicleDeliveryNotePartHistoryDto>builder()
              .totalCount(page.getTotalElements())
              .count((long) page.getNumberOfElements())
              .startElement(searchDto.getLimit() * searchDto.getPage())
              .results(partsHistory)
              .build();
  }

oraz test

@Test
public void testGetVehicleDeliveryNoteParts() {
    // for
    SearchDto searchDto = SearchDto.builder().limit(50L)
            .page(0).build();

    VehicleDeliveryNotesCriteriaDto criteriaDto = VehicleDeliveryNotesCriteriaDto.builder()
            .vins(List.of("123"))
            .restrictedParts(true).build();

    // mock
    try (MockedStatic<RestrictedVdi4081Utils> utilities = Mockito.mockStatic(RestrictedVdi4081Utils.class)) {
        utilities.when(() -> RestrictedVdi4081Utils.isCodeRestricted("123"))
                .thenReturn(true);
    }

    Page<BigInteger> mockPage = new PageImpl<>(List.of(BigInteger.ONE, BigInteger.TWO));

    when(partHistoryRepository.findRestrictedIdentifiers(anyString(), eq(criteriaDto.getBuyerName()), eq(criteriaDto.getStartDate()), eq(criteriaDto.getEndDate()), anyInt(), any())).thenReturn(mockPage);
    when(partHistoryRepository.findAllByPartIdentifierIn(mockPage.getContent().stream().map(BigInteger::longValue).toList()))
            .thenReturn(getPartsHistory());

    FindResultDto<VehicleDeliveryNotePartHistoryDto> result = getVehicleDeliveryNoteService().getVehicleDeliveryNoteParts(searchDto, criteriaDto);

    // Sprawdzenie oczekiwanego zachowania
    assertEquals(3, result.getTotalCount());
    assertEquals(3, result.getCount());
}

I jeszcze zamockowany statyczny serwis

private static MockedStatic<SettingsService> utilities;
    @BeforeAll
    public static void init() {
        utilities = Mockito.mockStatic(SettingsService.class);
    }

    @AfterAll
    public static void close() {
        utilities.close();
    }
1

Test który napisałeś praktycznie nic nie testuje.

0

Asercje chciałem dodać potem. Najwięcej logiki jest w poniższym fragmencie, ale nie moge dojść do tego momentu, bo repozytorium zwraca nulla na wcześniejszym etapie.

 List<VehicleDeliveryNotePartHistoryDto> partsHistory = partHistoryRepository.findAllByPartIdentifierIn(identifiers).stream()
          .map(partHistoryMapper::mapEntityToDto)
          .filter(p -> criteriaDto.getVdi4081Code() == null || p.getVdi4081Code().equals(criteriaDto.getVdi4081Code()))
          .collect(Collectors.groupingBy(VehicleDeliveryNotePartHistoryDto::getPartIdentifier))
          .entrySet().stream()
          .map(this::collectHistoryData)
          .toList();
2
Tomek112271 napisał(a):

Asercje chciałem dodać potem. Najwięcej logiki jest w poniższym fragmencie, ale nie moge dojść do tego momentu, bo repozytorium zwraca nulla na wcześniejszym etapie.

Rozumiem Twoją frustrację, ale problem nie jest z tym repozytorium.

Twój główny problem jest taki że masz śmietnik w kodzie, i nie napisałeś testów najpierw, tylko zostawiłeś je na koniec. W ten sposób wytworzyłeś sobie kawałek nietestowalnego kodu (co jest zupełnie spodziewane jak piszesz testy później). Twój test jest pełen szczegółów implementacyjnych i tight-couplingu, żeby w ogóle spróbować wywołać kawałek testowanej logiki. Twoje testy próbują do Ciebie teraz wykrzyczeć "masz słaby design, popraw go" - a wtedy testy staną się łatwiejsze. Ja czytam ten kod i nie potrafię zrozumieć co on w ogóle ma robić.

Co do Twojego problemu że mock zwraca null, to pewnie masz zbyt wąskie constrainty. Daj wszędzie any(), i powinien zacząć zwracać wartości.

Ale potwarzam - pisanie takich testów w niczym Ci nie pomoże, tylko Cię spowolni.

3
  1. Wy tak żyjecie?
Tomek112271 napisał(a):
      if (Boolean.TRUE.equals(criteriaDto.getRestrictedParts())) {
  1. Zaloguj po prostu z czym trafiasz do metody (argumenty), bo może coś pokręciłeś - strzelam, że getStartDate czy getEndDate może mieć zaskakujące wartości. Np. jeśli jest to null - to tak jak napisałeś nie zadziała chyba (isNull)

  2. A najlepiej wywal to mockito i testuj jak człowiek - na kodzie, bo tego typu testy do niczego Cię nie doprowadzą. Tzn. może liczysz na medal za testowanie mockito, ale uwierz mi, nie masz szans z większością dużych software housów. Mistrzowie już dawno nauczyli się robić 100% pokrycia testami nie testując absolutnie niczego, ciężko będzie przebić.

0

Dokładnie problem był trywialny i wartości się nie zgadzały, ale czytając poprzednie wiadomości odnośnie sensu tych testów chciałbym zadać pytanie. Również dla mnie pisanie takich testów, gdzie 90% rzeczy to mockowanie totalnie mija się z celem. Takie testy nic nie wnoszą i samo pisanie testu zajmuje mi więcej czasu niż pisanie metody. Na ten moment nie ma u mnie presji na 100% pokrycia testami, ale zastanawiam się, jak to wygląda w innych projektach? Ja najchętniej bym pisał testy integracyjne, zaciągał kontekst aplikacji, używał test containersów do połączenia z bazą danych. Dłuższe budowanie aplikacji nie jest chyba aż takim problemem? Przecież przy lokalnym działaniu można je pomijać i tylko przy pipeline je uruchomić. Takie testy dla mnie by miały sens, ale na ten moment u mnie w projekcie to odpada, czy jest jakieś inne podejście, oprócz mockowania 90%, ale też bez tworzenia całego kontekstu?

1

Zamiast robić mocki repozytoriów możesz stworzyć repozytoria in-memory.
Czyli bierzesz ten interfejs, który Spring implementuje swoją magią, i piszesz jego własną implementację, gdzie pod spodem będzie np. HashMapa lub lista.
Wtedy w teście można to repozytorium zainicjalizować potrzebnymi danymi, przekazać do serwisu i "normalnie" przetestować serwis.

Napisałem kiedyś automatyczne tworzenie implementacji in-memory dla Szpringowych repozytoriów. Co prawda jest tam oranie refleksjami (z czym się w praktyce nie spotkałem), ale może będzie to jakaś inspiracja :D https://github.com/Potat0x/nomock

1
Tomek112271 napisał(a):

Ja najchętniej bym pisał testy integracyjne, zaciągał kontekst aplikacji, używał test containersów do połączenia z bazą danych. Dłuższe budowanie aplikacji nie jest chyba aż takim problemem? Przecież przy lokalnym działaniu można je pomijać i tylko przy pipeline je uruchomić. Takie testy dla mnie by miały sens, ale na ten moment u mnie w projekcie to odpada, czy jest jakieś inne podejście, oprócz mockowania 90%, ale też bez tworzenia całego kontekstu?

Tak, testcontainers i do przodu. Czasem faktycznie testy się wydłużają, ale raczej jak masz więcej rzeczy niż tylko bazę danych (kolejki, serwisy itp).

Czemu odpada Ci to w projekcie?

Btw. mockito też jest dla ludzi, tzn. są przypadki, że jest przydatne i przynosi więcej pożytku niż szkody - tylko, że to baaardzo, bardzo rzadkie przypadki.

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