backend-tech-forge / benchmark

A enterprise level performance testing solution. Taking inspiration from nGrinder, this project aims to develop a Spring Boot application mirroring nGrinder's functionality as closely as feasible.
MIT License
4 stars 0 forks source link

WebClient 모킹 에러 #42

Closed ghkdqhrbals closed 6 months ago

ghkdqhrbals commented 6 months ago

현재 저희는 WebClientConfig 에서 WebClient 를 빈으로 등록하고 TestResultService 에서 이를 주입받아서 사용하고 있습니다. 그리고 이 WebClient 의 baseUrl 은 localhost:8080 으로 설정되어있죠. 그래서 TestResultService 를 테스트 하기 위해서는 localhost:8080 에 대한 요청과 응답을 모킹하여 진행해야합니다.

그래서 한가지 방법을 가져와보았는데요. 바로 MockWebServer 를 통해서 (1) 모킹 서버를 열고(랜덤포트), 해당 서버로부터 들어오는 (2) 요청과 응답을 사전에 설정할 수 있는 okhttp3.mockwebserver 라이브러리를 사용해보았습니다.

아래와 같이 코드를 작성하게 된다면 문제없이 진행되었습니다. 응답으로 Hello, World! 가 도착합니다.

    @Test
    void test() {
        // MockWebServer를 초기화합니다.
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.start();

        // MockWebServer의 URL을 WebClient에 설정합니다.
        WebClient webClient = WebClient.builder().baseUrl(mockWebServer.url("/").toString()).build();

        // MockWebServer에 대한 응답을 정의합니다.
        mockWebServer.enqueue(new MockResponse().setBody("Hello, World!"));

        // WebClient를 사용하여 MockWebServer로 HTTP 요청을 보냅니다.
        Mono<ResponseEntity<String>> responseMono = webClient.get()
                .uri("/")
                .retrieve()
                .toEntity(String.class);
    }

하지만 !

저희는 TestResultService 가 주입받는 WebClient 를 모킹서버의 주소로 바꿔주어야합니다. 그래서 생성자를 통해 주입시켰는데도 여전히 모킹서버 주소(localhost:랜덤포트)가 아닌, 기존 WebClient 빈의 baseUrl 인 localhost:8080 로 요청을 전송합니다ㅜㅜ.

@LeeJeongGi Help!!!!!

LeeJeongGi commented 6 months ago

테스트만을 위한 설정이 필요한걸까요?!

ghkdqhrbals commented 6 months ago

넵. 외부 API 와 연동하는 테스트를 진행할려면 이 부분 해결이 필요할 것 같습니다.

ghkdqhrbals commented 6 months ago

Solved

해결완료했습니다.

알고보니 TemplateUtil 의 createRequest 메소드에서 testTemplate.getUrl() 에 webClient 요청을 보내고 있었더라구요ㅜㅜ. 저는 왜 자꾸 생성자를 통한 외부 의존성 주입을 진행했는데도 localhost:8080 으로 요청을 전달하지? 라는 의문을 가지고 있었는데, 기존 설정된 url 로 전송하도록 되어있는 createRequest 메소드 내부 로직으로 인해 그런거였습니다...

이제 테스트 코드 내부에서 template 을 생성할 때, url 을 mockServer 의 url 로 설정한 뒤 테스트하시면 될 것 같습니다 :) 아래는 예시입니다.

    ...

    @BeforeEach
    public void setUpEach() {
       // webClient 를 따로 mock 서버 url 로 설정합니다.
        WebClient webClient = WebClient.builder().baseUrl(mockBackEnd.url("/").toString()).build();
       // 생성자를 통해 따로 webClient 를 주입합니다.
        testResultService = new TestResultService(testTemplateRepository, testResultRepository,
            tpsRepository, mttfbRepository, testErrorLogRepository, userGroupRepository,
            webClient, new RequestCounter());
    }

    ...

    @Test
    @DisplayName("성능 측적 method 호출 시 결과 저장 후 반환 확인하는 테스트")
    public void getMethodTemplateResultTest() throws InterruptedException {

        //given
        Optional<TestTemplateResponseDto> template = saveGetTempData(mockBackEnd.url("/").toString());
        // 반환하고자 하는 stub 응답들을 응답큐에 삽입합니다.
        mockBackEnd.enqueue(new MockResponse().setBody("Hello, World0"));
        mockBackEnd.enqueue(new MockResponse().setBody("Hello, World1"));
        mockBackEnd.enqueue(new MockResponse().setBody("Hello, World2"));
        mockBackEnd.enqueue(new MockResponse().setBody("Hello, World3"));
        mockBackEnd.enqueue(new MockResponse().setBody("Hello, World4"));

        //when
        TestResultResponseDto testResultResponseDto = testResultService.measurePerformance(
            "userGroup", template.get().getId(), "start");

        //then
        assertThat(testResultResponseDto.getMethod()).isEqualTo(template.get().getMethod());
        assertThat(testResultResponseDto.getUrl()).isEqualTo(template.get().getUrl());
        assertThat(testResultResponseDto.getTotalUsers()).isEqualTo(template.get().getVuser());

    }
ghkdqhrbals commented 6 months ago

좀 더 쓰기 편하도록 클래스를 만들고 이를 테스트 클래스가 extends 하도록 설정하였습니다.

/**
 * webClient 요청을 받을 mock 서버를 생성합니다
 */
public class MockServer {

    /**
     * Mock server instance
     */
    public static MockWebServer mockBackEnd;
    /**
     * Opened mock server url
     */
    public static String backendUrl;
    private ObjectMapper objectMapper = new ObjectMapper();

    @BeforeAll
    static void setUp() throws IOException {
        // webClient 요청을 받을 mock 서버를 생성합니다
        mockBackEnd = new MockWebServer();
        mockBackEnd.start();
        backendUrl = String.format(mockBackEnd.url("/").toString());
    }

    @AfterAll
    static void tearDown() throws IOException {
        mockBackEnd.shutdown();
    }

    /**
     * Mock server에 응답을 추가합니다
     *
     * <p> 만약 Object 를 Json String 으로 변환할 수 없다면 "" 값이 응답으로 반환됩니다</p>
     *
     * @param object
     */
    public void addMockResponse(Object object) {
        String json = "";
        try {
            json = objectMapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        MockResponse response = new MockResponse()
            .setHeader("Content-Type", "application/json")
            .setBody(json);
        mockBackEnd.enqueue(response);
    }

    /**
     * Mock server에 응답을 추가합니다. 반복 횟수를 지정할 수 있습니다.
     *
     * <p> 만약 Object 를 Json String 으로 변환할 수 없다면 "" 값이 응답으로 반환됩니다</p>
     *
     * @param object
     * @param repeatCount
     */
    public void addMockResponse(Object object, int repeatCount) {
        String json = "";
        try {
            json = objectMapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        MockResponse response = new MockResponse()
            .setHeader("Content-Type", "application/json")
            .setBody(json);
        for (int i = 0; i < repeatCount; i++) {
            mockBackEnd.enqueue(response);
        }
    }
}
LeeJeongGi commented 6 months ago

집가서 더 보긴할텐데,, 제가 오늘 아침에 확인했을 때 bm-common 폴더가 있는데 bm-agent에서 자꾸 빌드를 실패하고 있던데 이건 뭔가 설정이 따로 필요한걸까요?

제가 develop 그대로 pull 로 가져와서 확인해봐도 마찬가지더라구요!

LeeJeongGi commented 6 months ago

추가로 외부 api테스트를 진행 할 때 어떤걸 호출해야 적절한지 잘 모르겠어서 우선 규민님이 작성해주신 유저 api랑 제가 만든 템플릿 api랑 호출하는 식으로 테스트를 진행했었습니다!!

mockserver를 만들어서 테스트 해야 하는 부분까지는 제가 잘 몰랐던 부분이였어요. 좋은 정보 감사합니다!!

ghkdqhrbals commented 6 months ago

집가서 더 보긴할텐데,, 제가 오늘 아침에 확인했을 때 bm-common 폴더가 있는데 bm-agent에서 자꾸 빌드를 실패하고 있던데 이건 뭔가 설정이 따로 필요한걸까요?

제가 develop 그대로 pull 로 가져와서 확인해봐도 마찬가지더라구요!

아마 gradle 싱크 맞추실 때 깨져있을 수도 있습니다.

image

루트 프로젝트가 bm-agent, bm-common 을 포함하고 있어야합니다. 이를 위해서는 루트의 settings.gradle 에 include ':bm-agent', include ':bm-common' 를 적어주어야합니다.

그리고 루트 프로젝트 밖에 따로 선언되어있다면 intellij 가 따로 잡아놓은 것이니 이를 - 버튼으로 제거해주면 됩니다.

추가로 외부 api테스트를 진행 할 때 어떤걸 호출해야 적절한지 잘 모르겠어서 우선 규민님이 작성해주신 유저 api랑 제가 만든 템플릿 api랑 호출하는 식으로 테스트를 진행했었습니다!!

mockserver를 만들어서 테스트 해야 하는 부분까지는 제가 잘 몰랐던 부분이였어요. 좋은 정보 감사합니다!!

넵! 쓰기 편하도록 MockServer 를 따로 설정해서 편하게 쓰실 수 있또록 만들었어요ㅎㅎ