Home 외부 API 목으로 대체하기
Post
Cancel

외부 API 목으로 대체하기

외부 API를 목으로 대체하는 것에 대해 학습하며 작성된 글입니다. 학습 과정에서 작성된 글이기 때문에 잘못된 내용이 있을 수 있으며, 이에 대한 지적이나 피드백은 언제든 환영입니다.

image









1. 외부 API를 사용하는 경우와 문제점


깃허브를 통해 회원가입하는 시나리오를 살펴보겠습니다. 사용자가 회원가입 요청(1)을 보내면 API Gateway(2)를 지나 API 서버(3)로 접근하게 됩니다. API 서버는 깃허브 서버로 요청을 보내 액세스 토큰을 받아오고(4), 해당 액세스 토큰으로 사용자 정보를 요청(5)합니다. 마지막으로 깃허브로 부터 받아온 사용자 정보를 데이터베이스에 저장(6)한 후 응답을 반환합니다.

image









위 시나리오를 테스트할 때 깃허브와 같이 외부 API 요청이 있다면 이는 어떻게 테스트해야 할까요? 테스트할때마다 실제 서버로 요청을 보내면 깃허브의 응답결과에 따라 테스트가 실패할 수 있습니다. 즉 기존 테스트(주황색)가 외부 API의 결과(파란색 대역)에 따라 영향을 받게 됩니다.

image









이 문제를 해결하기 위해서는 깃허브로 부터 오는 응답 결과(파란색 대역)를 일정하게 반환헤서 기존 테스트가 영향을 받지 않게 해야 합니다. 즉 일정한 결과를 받으려면 이를 반환해주는 목 서버가 필요한 것입니다. 목 서버를 만드는 것에대해 살펴보겠습니다.

image









2. 목 서버 구축과 한계


목 서버를 구축하는 방법이야 여러가지가 존재하지만 포스트맨(postman)과 실제 서버 구축 두 가지에 대해 간략히 살펴보겠습니다.





2-1. 포스트맨(Postman)

포스트맨으로 목 서버를 구축하는 방법입니다. 월 1,000건 까지 무료이며 방법이 간단하다는 장점이 있습니다. 하지만 1,000건이 넘었을 때 유료라는 점과 목 서버의 주소가 바뀌거나 서버가 내려가면 응답을 받을 수 없는 불안전한 단점이 있습니다.

image

포스트맨으로 목 서버를 구축하는 구체적 방법에 대해서는 해당 링크를 참조해주세요.









2-2. 실제 서버 구축

두 번째 방법은 실제 테스트 서버를 구축하는 것입니다. 아래와 같이 같은 가용영역 안에 하위 도메인을 만들어 테스트 서버를 두면 외부 API를 호출할때 해당 서버로 주소를 돌리면 됩니다. 같은 가용영역에, 하위 도메인으로 이를 두는 이유는 쿠키가 하위 도메인으로 밖에 전송되지 않는 이슈가 있을 수 있기 때문입니다. 이렇게 테스트 서버를 두게되면 운영환경에 가깝게 테스트할 수 있는 장점이습니다. 하지만 서버를 추가로 구축해야 한다는 점, 테스트 서버 관리 비용이 발생한다는 단점이 있습니다.

image









3. 대안


WireMock은 이런 문제를 해결하기 위해 존재하는 특정 대역을 목으로 대체해서 일정한 응답을 돌려주는 라이브러리입니다. 즉 파란색 외부 API 대역이 일정한 응답결과를 반환하도록 설정하고, 외부 API 호출이 발생하면 세팅해둔 결과 값을 반환하는 것입니다. 이를통해 기존에 작성한 코드(주황색 대역)가 외부 API의 응답 결과(파란색)에 영향을 받지 않게 됩니다.

image









이론은 알았으니 코드레벨로 넘어가보겠습니다. 우선 WireMock을 사용하기 위해 의존성을 추가해줍니다.

1
testImplementation("org.springframework.cloud:spring-cloud-starter-contract-stub-runner")









랜덤 포트를 사용해서 조금 더 실환경과 가까운 테스트를 할 수 있도록 @AutoConfigureWireMock(port = 0)를 붙여줍니다. 이를 통해 WireMock 서버는 스프링이 설정한 랜덤 포트의 번호를 동일하게 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
// https://stackoverflow.com/questions/48707625/set-property-with-wiremock-random-port-in-spring-boot-test
// https://stackoverflow.com/questions/49374115/how-can-i-make-wiremock-port-more-dynamic-to-use-it-for-testing-service
@ActiveProfiles("test")
@ExtendWith(RestDocumentationExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWireMock(port = 0)
@TestPropertySource(
        properties = {"movieapp.baseUrl=http://localhost:${wiremock.server.port}"}
)
public abstract class AbstractTestConfiguration {
    
}









이후 아래와 같이 목 서버에서 반환할 응답 값을 지정해주면 외부 API 응답이 내가 원하는 결과대로 오게 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public final class MoviesStub {

    public static void setStub() throws IOException {
        stubFor(get(urlEqualTo("/all-movies"))
                .willReturn(WireMock.aResponse()
                        .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                        .withStatus(HttpStatus.OK.value())
                        .withBody(getMockResponseByPath("__files/all-movies.json"))));
    }

    private static String getMockResponseByPath(String path) throws IOException {
        return copyToString(getMockResourceAsStream(path), defaultCharset());
    }

    private static InputStream getMockResourceAsStream(String path) {
        return MoviesStub.class.getClassLoader().getResourceAsStream(path);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@AutoConfigureWireMock(port = 0)
@TestPropertySource(
        properties = {"movieapp.baseUrl=http://localhost:${wiremock.server.port}/all-movies"}
)
class SaveRecentlyReleasedMoviesAcceptanceTest extends AbstractTestConfiguration {

    @BeforeAll
    static void beforeAll() throws IOException {
        MoviesStub.setStub();
    }

    @Test
    @DisplayName("새로 개봉한 영화 목록을 CGV로부터 받아왔다면 영화 목록을 조회할 수 있다.")
    void 새로_개봉한_영화_저장_인수_테스트() {
        ExtractableResponse<Response> response = given(this.specification)
                .contentType(ContentType.JSON)
                .when()
                .get("/api/movies/command")
                .then().extract();

        assertNotNull(response.body());
    }
}









4. 정리


외부 API를 호출하는 코드를 테스트하기 위해서는 외부 API로 부터 오는 응답 값이 일정해야 합니다. 외부 API의 결과 값에 따라 기존 테스트가 영향을 받게 되면 불안전한 테스트가 되기 때문입니다. 따라서 외부 API로 부터 오는 응답을 일정하게 유지하도록 목으로 대체해서 안정적인 테스트 코드를 작성 하는 것이 필요합니다.

    - WireMock for Java Developers
    - ThomasKasene/wiremock-junit-extension
    - JensPiegsa/wiremock-extension
    - To Mock or not to Mock, that is the question…



This post is licensed under CC BY 4.0 by the author.

[Unit Testing] Integration Test

MySQL FK 컨벤션