WebClient 테스트 - MockWebServer를 이용해 외부API 호출 메서드 테스트

MockWebServer이란? MockWebServer 사용하는 방법

개요

최근에 대부분의 서비스에서는 REST API를 호출할 것으로 예상된다. Spring에서는 REST 클라이언트 구축을 위한 몇 가지 옵션을 제공하며 WebClient 를 권장히고 있다.

여기서는 WebClient를 사용하여 API를 호출 하는 서비스를 단위 테스트 하는 방법에 대해 알아 보겠다.

MockWebServer란?

Square 팀에서 만든 MockWebServer는 HTTTP Request를 받아서 Response를 반환하는 간단하고 작은 웹서버이다.

우리가 작성하는 메서드 중에 WebClient, RestTemplete 등과 같은 Client를 사용하여, Http를 호출하는 메서드의 테스트 코드를 작성할때 이 MockWebServer를 호출하게 함으로써 쉽게 테스트 코드를 작성할 수 있다. 실제로 Spring Team도 MockWebServer를 사용하여 테스트하라고 권장한다고 한다.

간단한 WebClient 프로젝트 생성

MockWebServer 사용해 보기 앞서 먼저 테스트 대상의 간단한 WebClient를 사용한 프로젝트를 생성하도록 하겠다.

신규 프로젝트 생성 명령

아래와 같이 curl 명령어를 사용하여 Spring Boot 신규 프로젝트를 생성한다.

curl https://start.spring.io/starter.tgz \
-d bootVersion=2.7.6 \
-d dependencies=webflux \
-d baseDir=spring-mockwebserver \
-d groupId=com.devkuma \
-d artifactId=spring-mockwebserver \
-d packageName=com.devkuma.mockwebserver \
-d applicationName=MockWebserverApplication \
-d javaVersion=11 \
-d packaging=jar \
-d type=gradle-project | tar -xzvf -

위 명령에서는 Java 11, Gradle, Spring Boot 버전은 2.7.6을 사용하였고, WebClient를 사용하기 위해 의존성으로 “webflux"를 추가하였다.

외부 연결하는 서비스 코드 생성

데이터를 전달 받을 DTO 객체를 생성한다.

package com.devkuma.mockwebserver.dto;

public class User {

    private int id;
    private String name;

    public User() {
    }

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

WebClient를 활용한 서비스 코드를 생성한다.

package com.devkuma.mockwebserver.service;

import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

import com.devkuma.mockwebserver.dto.User;

import reactor.core.publisher.Mono;

public class UserService {

    private final WebClient webClient;

    public UserService(WebClient webClient) {
        this.webClient = webClient;
    }

    public Mono<User> getUserById(Integer userId) {
        return webClient
                .get()
                .uri("/users/{id}", userId)
                .retrieve()
                .bodyToMono(User.class);
    }
}

MockWebServer 적용

MockWebServer 의존성 추가

MockWebServer를 사용하기 위해 의존성 라이브러리는 추가한다.

dependencies {
    // ... 중간 생략 ...
	testImplementation 'com.squareup.okhttp3:mockwebserver'
}

테스트 코드에 MockWebServer 추가

테스트 코드에 MockWebServer를 사용하기 위한 간단한 설정을 추가한다.

package com.devkuma.mockwebserver.service;

import java.io.IOException;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;

import okhttp3.mockwebserver.MockWebServer;

public class UserServiceTest {

    public static MockWebServer mockWebServer;

    @BeforeAll
    static void setUp() throws IOException {
        mockWebServer = new MockWebServer();
        mockWebServer.start();
    }

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

}

테스트 코드에 @BeforeAll, @AfterAll를 추가하였다.
@BeforeAll 메소드에는 MockWebServer 객체를 생성하고 기동하고 있고, @AfterAll 메소드에는 서버를 종료한다.

서비스에 WebClient를 생생하여 전달

기본 URL를 넣어 WebClient를 생성하여, 서비스에 전달한다.

@BeforeEach
void initialize() {
    final String baseUrl = String.format("http://localhost:%s", mockWebServer.getPort());
    final WebClient webClient = WebClient.create(baseUrl);
    userService = new UserService(webClient);
}

이렇게 하면 테스트가 동작을 하게 되면 실제 외부 URL를 호출하는 것이 아니라, MockWebServer를 호출하게 된다.

테스트 코드를 작성 임의의 Response 생성

이제는 코드를 작성해 보겠다.

@Test
void getUserById() throws Exception {
    // given
    final User mockUser = new User(3, "devkuma");
    mockWebServer.enqueue(new MockResponse()
                                    .setBody(objectMapper.writeValueAsString(mockUser))
                                    .addHeader("Content-Type", "application/json"));

    // when
    final Mono<User> userMono = userService.getUserById(3);

    // then
    StepVerifier.create(userMono)
                .expectNextMatches(employee -> mockUser.getName().equals("devkuma"))
                .verifyComplete();
}

given 부분에는 MockResponse 타입으로 Stub Response를 생성하겨, API가 호출시에 반환할 수 있도록, enqueue에 넣었다. enqueue에 넣어주면 MockWebServer는 엔큐된 순서대로 응답을 반환하게 된다.

Request에 대한 체크

그리고 응답값 뿐 아니라 요청에 대한 값도 아래와 같이 체크할 수 있다.

final RecordedRequest recordedRequest = mockWebServer.takeRequest();
assertAll(
        () -> assertEquals("GET", recordedRequest.getMethod()),
        () -> assertEquals("/users/3", recordedRequest.getPath())
);

참조


위에 예제 코드는 GitHub에서 확인해 볼 수 있다.



최종 수정 : 2022-12-17