본문 바로가기
spring & boot/Spring & Spring Boot

[Spring Boot] WireMock - API Test

by lucas_owner 2024. 10. 29.

WireMock을 이용한 API 서버 Test

- 다양한 프로젝트를 진행하다보면 Server To Server 통신을 진행해야만 하는 상황들이 발생한다(소셜로그인, MSA 아키텍처구조 ...)

이럴때 테스트 대상이되는 모든 서버를 Local 에 구동시킨채 Test 를 진행하기에는 많은 자원낭비, 많은 시간소요 등 불편한점이 많다.

특히 Test Code 를 작성해서 테스트마저 자동화를 많이 하고 있는 추세이기 때문이다. 

 

WireMock 이란

- Http 기반의 API 서비스를 Mocking 하기 위한 라이브러리이다.

즉, 외부 서비스에 의존하는 테스트를 진행할때, 외부 서비스의 Response(응답)을 Mocking 하여 테스트를 진행할 수 있게 해준다.

(외부 서비스에서 받아올 응답을 미리 지정해둔 형태로 Test 진행)

 

테스트 단계중 단순 외부서비스와의 통신을 통해 진행해야 하는 경우, 의존하지 않고 진행할 수 있다는점에서 테스트하기에 용이하다고 생각하여 적용하게 되었다. 

 

 

WireMock 사용 방법

일반적으로 WireMock 을 사용하는 방법에는 2가지 정도가 존재한다.

  • Docker, 또는 Local 에 별도 서버를 구동하는 Standalone 방식(Jar 파일 사용)
    • Test Code 를 작성하지 않고 사용할때 사용
  • Spring에 의존성을 추가하여, JUnit 을 사용한 테스트시 진행하는 방법

해당 글에서는 2번째 방법인 JUnit 을 통해 Test 를 진행해 보도록 하겠다.

 

 

WireMock 적용

일반적으로 의존성을 추가하듯이 build.gradle 에 의존성을 추가해주면 된다.

testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-stub-runner:4.1.4'

 

Version?

- WireMock 이나 다른 라이브러리들과 같이 공식 문서를 통해 사용하는 Spring 버전별로 버전을 맞춰주면 되는데

현재 가장 최신 버전은 4.1.4 버전이다. (4.2.0-M1 정식 릴리즈가 아닌 버전이 존재하긴한다. 2024.10 기준)

 

spring Webflux 3.x 버전 부터는 4.0.5 버전부터 지원 가능하다!

https://github.com/spring-cloud/spring-cloud-contract/releases

 

Stub(응답 지정)

WireMock 설명에서도 언급했었지만, Http 응답값을 미리 지정해두고 사용한다고 했었다. 

이때 Stub 이란 http 요청 경로와 그에 대한 응답을 설정해서 제공하는것을 의미한다.

일반적으로 응답 File 을 미리 생성해두고 사용하며 Http 요청/응답 에 대한 상세 설정 및 

단순 응답 데이터를 설정해서 사용할 수도 있다. 

 

예제에서는 단순 JSON 데이터를 File 로 만들어서 사용했다.

{
  "movieInfoId": "1",
  "name": "Batman Begins",
  "year": 2005,
  "cast": [
    "Christian Bale",
    "Michael Cane"
  ],
  "release_date": "2005-06-15"
}

 

해당 파일들의 경우 /test/resources/__files 가 기본 경로이며, 별도의 Directory 를 지정하여 사용할 수도 있다.

default path

.willReturn
...
.withBody(getMockResponseBodyByPath("custom/file.json")

// 혹은
@AutoConfigureWireMock(stubs="classpath:/custom")

 

WireMock 을 적용한 전체 테스트코드

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("local")
@AutoConfigureWebTestClient
@AutoConfigureWireMock(port = 8084) // Spin up a WireMock server on port 8084
@TestPropertySource(
        properties = {
                "restClient.moviesInfoUrl=http://localhost:8084/api/movieInfos",
                "restClient.reviewUrl=http://localhost:8084/api/reviews"
        }
)
class MoviesControllerIntgTest {

    @Autowired
    WebTestClient webTestClient;

    @Test
    @DisplayName("Retrieve Movie By ID: 200 OK")
    void retrieveMovieById() {
        var movieId = "abc";
        // MovieInfo: you can receive response, If you request this Specific Url
        stubFor(get(urlEqualTo("/api/movieInfos/" + movieId))
                .willReturn(aResponse()
                        .withHeader("Content-Type", "application/json")
                        .withBodyFile("movieinfo.json")));

        // reviews: you use to QueryParam, But We can use Easy way to get response
        // UrlPathEqualTo is, if path is same, you can get response -> not need to QueryParam
        stubFor(get(urlPathEqualTo("/api/reviews"))
                .willReturn(aResponse()
                        .withHeader("Content-Type", "application/json")
                        .withBodyFile("reviews.json")));

        webTestClient.get()
                .uri("/api/movies/{id}", movieId)
                .exchange()
                .expectStatus().isOk()
                .expectBody(Movie.class)
                .consumeWith(movieEntityExchangeResult -> {
                    var result = movieEntityExchangeResult.getResponseBody();

                    assert Objects.requireNonNull(result).getReviewsList().size() == 2;
                    assertEquals("Batman Begins", result.getMovieInfo().getName());
                });
    }
}

 

우선 WireMock 을 사용하여 Mock Server 테스트를 진행하기 위해서는

@AutoConfigureWireMock 어노테이션을 사용해야 한다.

- Mock 서버를 Bean 으로 등록하고 사용하기 위함

 

@AutoConfigureWireMock(port = 8084) // Spin up a WireMock server on port 8084
@TestPropertySource(
        properties = {
                "restClient.moviesInfoUrl=http://localhost:8084/api/movieInfos",
                "restClient.reviewUrl=http://localhost:8084/api/reviews"
        }
)

Annotation 과 properties 를 보면 이해하기 쉬울것이다.

port = {포트 지정} 을 통해 특정포트를 지정할 수 있으며
default = 8080 설정이다.
Random 한 port 설정시 port = 0 으로 설정하면 된다.

 

 

아래의 @TestPropertySource 의 properties 는 요청을 보낼 Mock Server 의 주소인걸 알 수 있다.

유의 할점은 위의 예제처럼 port 를 지정한다면 port 를 매핑시켜 사용하면 되지만, 랜덤한 포트를 사용할 경우는

아래처럼 사용할 수 있다.

@AutoConfigureWireMock(port = 0)
@TestPropertySource(
        properties = {
                "restClient.moviesInfoUrl=http://localhost:${wiremock.server.port}/api/movieInfos",
                "restClient.reviewUrl=http://localhost:${wiremock.server.port}/api/reviews"
        }
)

 

 

WireMock Test Code 작성

stubFor(get(urlEqualTo("/api/movieInfos/" + movieId))
                .willReturn(aResponse()
                        .withHeader("Content-Type", "application/json")
                        .withBodyFile("movieinfo.json")));


stubFor(get(urlPathEqualTo("/api/reviews"))
        .willReturn(aResponse()
                .withHeader("Content-Type", "application/json")
                .withBodyFile("reviews.json")));

stubFor() 이라는 static Method 를 사용하며 get,post,update,delete 와 같은 Http 요청을 Test 할 수 있다.

 

위처럼 withBodyFile() 를 통해 응답값 파일을 직접 지정할 수도 있으며, 아래처럼 특정 경로의 파일을 지정해줄수도 있다.

.withBody(getMockResponseBodyByPath("custom/file.json")

 

위의 2가지 예제를 본다면 Url Method 가 다른것을 볼 수 있는데 

 

urlEqualTo: path, 쿼리 파라미터를 포함하여 URL 전체가 지정한 값과 정확히 일치하는 경우
urlMatching: URL 전체를 정규 표현식(Regex) 를 사용하여 매칭(/api/movieinfos.*)
urlPathEqualTo: path 만 일치시키며 Query Parameter 는 무시한다.
urlPathMatching: path 부분만 정규표현식으로 매칭하며, Query Parameter 를 무시한다(/api/.*)

 

 

이후 Test 를 진행하게 되면 WireMock 에서의 Response 가 Log 로 출력되게 된다.


간단하게 JSON 값만을 테스트 하는 방법들을 소개했었는데, 실제 Http Response 를 응답값으로 지정하여 사용하면 

더욱 정교한 Test 를 진행 할 수 있을것 같다.

반응형

댓글