luchob / softuni-spring-may-2024

Our common projects
15 stars 1 forks source link

Проблем с тестовете #57

Closed mihaela0608 closed 3 months ago

mihaela0608 commented 3 months ago

Връзка към проекта:(https://github.com/mihaela0608/TrackIt) Проблемът е с тестовете ми.

  1. Очаквам да мога да правя свободно тестовете си, но дори contextLoads теста не минава.
  2. При зареждане с rest клиента се опитва да направи заявка, което не минава тъй като другия проект не е стартирал.
  3. Първо проблема се случи, когато опитах да направя интеграционен тест за регистрацията, затова реших да се опитам да го реша директно при зареждането.

При стартиране на всички тестове, тестът за регистрация или тестът за contextLoads може да се види проблема.

luchob commented 3 months ago

Здравей!

Доколкото виждам и разбирам, проблемът се състои в това че използваш REST client извиквания във функционалности, които искаш да тестваш. Те съвсем естествено гърмят, защото другия проект не съществува по време на тестове. Например, при регистрацията - викаш външния сървиз.

Оттук нататък опциите са много разнообразни. Като първа стъпка, тъй като RestClient API-то е твърде трудно за mock-ване (както виждам си забелязала) може да изкараш цялата комуникация в отделен интеграционен сървис който да енкапсулира REST Client-a. Например, вместо:

private void addLastMonthProfile(User user) {
        UserData userData = new UserData(user.getId());
        userData.setLastMonthSavings(BigDecimal.ZERO);
        userData.setLastMonthExpenses(BigDecimal.ZERO);
        restClient.post()
                .uri("http://localhost:8081/api/userdata")
                .body(userData)
                .retrieve();
    }

да бъде:

private void addLastMonthProfile(User user) {
        UserData userData = new UserData(user.getId());
        userData.setLastMonthSavings(BigDecimal.ZERO);
        userData.setLastMonthExpenses(BigDecimal.ZERO);

        userDataService.saveUserData(userData);
    }

И вътре в userDataService да си работиш с restClient-a. Така вместо да се пробваш да мокнеш restClient (което е трудно) може да мокваш userDataService (което е лесно).

Оттам нататък имаш различни опции:

  1. Използвай mockbean. Само че, виждам че го правиш малко грешно.
@SpringBootTest <--- това е за integration test-ове
@ExtendWith(MockitoExtension.class) <---- това е за unit тестове

Двете заедно - тц. Може би така става:

@SpringBootTest
class TrackItApplicationTests {
    @MockBean // това е мок, който се слага в AppicationContext-a на мястото на истинския бийн.
    private RestClient restClient;
    @Mock
    private RestClient.RequestBodyUriSpec requestBodyUriSpec;// това е най обикновен, мок, нищо не прави, никъде не се слага за сега
    @Mock
    private RestClient.RequestHeadersSpec<?> requestHeadersSpec;

Т.е. mockbean замества истински бийн в application context-a

  1. Можеш да си направиш още един @Primary бийн в тестовите класове, който да замести истинския в контекста :-)

  2. Можеш да мокнеш целия външен сървис с wiremock. Виж как го направихме тук.

Поздрави, Л.

luchob commented 3 months ago

BTW, за справка - изпращането на REST заявка по време на ДБ транзакция е голям проблем в практиката. :-) Защо?

Защото имаш няколко варианта как да стане това:

1.

Начало на транзакция. REST заявка. Край на транзакция.

Проблем - REST заявката може да мине, но транзакцията да фейлне.

2.

REST заявка. Начало на транзакция. Край на транзакция.

Проблем - REST заявката може да мине, но транзакцията да фейлне.

3.

Начало на транзакция. Край на транзакция. REST заявка.

Проблем - транзакцията може да мине, но REST заявката може да фейлне.

:-) По нормален начин няма как да го решиш, затова има един патърн който се нарича Transactional outbox.

outbox

В рамките на транзакцията записваш каквото е необходимо - например юзъра. В outbox таблица в същата транзакция записваш какво трябва да се прати по рест. Асинхронно, например с schduler изпращаш по рест каквото е необходимо, с необходимите retry механизми и т.н.

Поздрави, Л.

mihaela0608 commented 3 months ago

Много благодаря за помощта. Наистина опитах толкова начини за мокването на рест клиента и не се получаваше. Благодаря Ви много!

На ср, 24.07.2024 г., 19:40 ч. Lachezar Balev @.***> написа:

BTW, за справка - изпращането на REST заявка по време на ДБ транзакция е голям проблем в практиката. :-) Защо?

Защото имаш няколко варианта как да стане това:

1.

Начало на транзакция. REST заявка. Край на транзакция.

Проблем - REST заявката може да мине, но транзакцията да фейлне.

2.

REST заявка. Начало на транзакция. Край на транзакция.

Проблем - REST заявката може да мине, но транзакцията да фейлне.

3.

Начало на транзакция. Край на транзакция. REST заявка.

Проблем - транзакцията може да мине, но REST заявката може да фейлне.

:-) По нормален начин няма как да го решиш, затова има един патърн който се нарича Transactional outbox.

outbox.png (view on web) https://github.com/user-attachments/assets/c4421473-f2a6-4875-b421-a947bb273a7a

В рамките на транзакцията записваш каквото е необходимо - например юзъра. В outbox таблица в същата транзакция записваш какво трябва да се прати по рест. Асинхронно, например с schduler изпращаш по рест каквото е необходимо, с необходимите retry механизми и т.н.

Поздрави, Л.

— Reply to this email directly, view it on GitHub https://github.com/luchob/softuni-spring-may-2024/issues/57#issuecomment-2248463479, or unsubscribe https://github.com/notifications/unsubscribe-auth/A74YTMUCJOSIJVDMNUOVAQDZN7KG5AVCNFSM6AAAAABLMZLUWSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENBYGQ3DGNBXHE . You are receiving this because you authored the thread.Message ID: @.***>