0. 글을 쓰게 된 이유
운영 업무를 진행하면서 외부 시스템과 통신하여 값을 받아오는 기능을 테스트해야 하는 상황이 있었다.
하지만 실제 외부 서버를 호출하지 않고 테스트를 진행하려면 어떤 방식으로 모킹을 해야 하는지 고민이 되었고, 이 과정에서 WireMock이라는 도구를 알게 되었다.
이번 글에서는 WireMock이 무엇인지, 왜 필요했고 어떻게 사용하게 되었는지 정리해보려고 한다.
1. 필요했던 상황
운영중인 서버 기능 중 외부에 있는 여러 서버들의 상태를 조회하여 대시보드에 노출시키는 기능이 있었다.
어느 날 특정 케이스에서 로그 상 Exception이 발생했다는 알림이 감지되었지만, 서버 응답 처리 로직의 오류로 인해 실패 응답이 성공으로 저장되는 문제가 있었다.
그 결과 실제로는 오류가 발생했음에도 불구하고 대시보드 상에서는 정상 상태로 표시되는 현상이 확인되었다.
문제를 수정하기 위해 내부 로직을 확인하고 테스트가 필요했지만, 운영중인 서비스의 구조상 내부 테스트 케이스를 직접 작성하기 어려운 상황이었다.
외부 서버 응답을 제어할 수 있는 환경이 필요했고, 이를 위해 Mock 서버를 띄워 시험 환경에서 테스트를 진행해야 했다.
2. 기존 방식의 문제
문제는 외부 서버 요청에 대한 테스트를 정상적으로 수행할 수 있는 방법이 없다는 점이었다.
해당 기능은 실제 외부 서버에 요청을 보내고 응답을 받아 처리하는 구조였기 때문에, 로컬 환경이나 시험 환경에서 특정 응답을 강제로 만들어 테스트하기가 어려웠다.
특히 오류 응답이나 예외 상황을 재현하려면 외부 서버에서 동일한 상황이 발생해야 했고, 이를 테스트 환경에서 의도적으로 만들 수 없다는 문제가 있었다.
결국 외부 요청에 대한 응답을 직접 제어할 수 있는 방법이 필요했고, 외부 서버를 대신할 수 있는 Mock 서버가 필요하다는 결론에 도달했다.
3. 기술 자료 조사 및 WireMock 선택 이유
외부 요청을 직접 제어할 수 있는 방법을 찾기 위해 Mock 서버 관련 기술들을 다시 찾아보게 되었다.
이전에 Mockito 관련 테스트를 학습할 때나, OAuth 프레임워크를 구성하면서 테스트 환경을 만들 때에도 비슷한 고민을 했던 적이 있었다.
그 당시 WireMock이라는 라이브러리를 알고는 있었지만, 실제로 서버를 구성해서 테스트까지 진행해 본 적은 없었다.
이번 장애 대응을 계기로 Mock 서버를 직접 구성해 보면서 WireMock뿐 아니라 MockServer라는 도구도 함께 알게 되었다.
두 가지를 비교해 본 결과, 이번과 같이 특정 요청에 대해 응답을 정의하고 시험 환경에서 빠르게 검증해야 하는 상황에서는
WireMock이 설정이 간단하고 사용이 직관적이라 더 적합하다고 판단했다.
다만 이후 통합 테스트 단계로 넘어가게 되면 여러 요청을 동시에 제어하거나 프록시 기반으로 동작시켜야 하는 경우도 있을 수 있기 때문에
MockServer 도입에 대해서도 추후 다시 검토해 볼 필요가 있다고 생각했다.
4. WireMock이란
WireMock은 HTTP 기반 Mock 서버를 쉽게 구성할 수 있도록 도와주는 라이브러리이다.
외부 서버 대신 응답을 내려주는 서버를 직접 띄울 수 있고, 요청 URL, Query, Header, Body 등의 조건에 따라 원하는 응답을 정의할 수 있다.
즉, 실제 외부 API를 호출하지 않고도 외부 서버가 응답한 것처럼 동작하도록 만들 수 있는 도구이다.
이번과 같이 외부 서버의 응답을 직접 제어하면서 특정 케이스를 재현해야 하는 상황에서 WireMock은 매우 유용하게 사용할 수 있다.
실제로 jar 파일을 실행하여 서버처럼 띄워 사용할 수도 있고, 요청과 응답을 .json 파일로 정의하여 Mock URL을 구성할 수도 있다.
또한 Admin API를 통해 매핑을 직접 등록할 수도 있어서 다양한 응답 케이스를 만들어 테스트 환경에서 반복적으로 사용할 수 있다는 점이 큰 장점이었다.
이러한 방식 덕분에 외부 서버를 제어하기 어려운 환경에서도 원하는 응답을 자유롭게 만들어 로직을 검증할 수 있었고, 이번 장애 대응 상황에도 적합한 도구라고 판단했다.
5. 사용 방법
필요 상황에서 WireMock을 사용하는 방법은 크게 두 가지가 있다.
하나는 애플리케이션 내부에 라이브러리 형태로 포함하여 사용하는 내재화 방식이고, 다른 하나는 standalone jar를 실행하여 별도의 서버로 띄워 사용하는 방식이다.
5.1 standalone 방식
WireMock은 jar 파일을 실행하면 HTTP 서버가 올라오고, mappings 폴더에 정의된 JSON 파일을 기준으로 요청과 응답을 매핑하여 동작한다.
먼저 WireMock 서버를 실행한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java -jar wiremock-standalone-3.x.x.jar --port 8089
서버가 실행되면 mappings 폴더에 요청과 응답을 정의한 JSON 파일을 작성한다.
ex)
{
"request": {
"method": "GET",
"url": "/external/message"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"message": "wiremock test success"
}
}
}
5.2 내재화 방식
내재화 방식 테스트를 위해 HttpClient를 호출하는 간단한 스프링 프로젝트 서비스를 구성했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
plugins {
id 'java'
id 'org.springframework.boot' version '3.5.12'
id 'io.spring.dependency-management' version '1.1.7'
}
group = 'com.wiremock'
version = '0.0.1-SNAPSHOT'
description = 'wiremock'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
ext {
set('springCloudVersion', "2025.0.1")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.cloud:spring-cloud-contract-wiremock'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
tasks.named('test') {
useJUnitPlatform()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class ExternalMessageService {
private final ExternalMessageClient externalMessageClient;
public ExternalMessageService(ExternalMessageClient externalMessageClient) {
this.externalMessageClient = externalMessageClient;
}
public String getMessage() {
return externalMessageClient.getMessage(null, null).message();
}
public String getMessageByTypeAndLang(String type, String lang) {
return externalMessageClient.getMessage(type, lang).message();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
@FeignClient(
name = "externalMessageClient",
url = "${external.api.base-url}"
)
public interface ExternalMessageClient {
@GetMapping("/external/message")
ExternalMessageResponse getMessage(
@RequestParam(value = "type", required = false) String type,
@RequestParam(value = "lang", required = false) String lang
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@SpringBootTest
@AutoConfigureWireMock(port = 0)
@TestPropertySource(properties = {
"external.api.base-url=http://localhost:${wiremock.server.port}"
})
class ExternalMessageServiceTest {
@Autowired
private ExternalMessageService externalMessageService;
@Test
void returnsMessageFromWireMockServer() {
WireMock.stubFor(get(urlEqualTo("/external/message"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody("""
{
"message": "wiremock test success"
}
""")));
String message = externalMessageService.getMessage();
assertThat(message).isEqualTo("wiremock test success");
WireMock.verify(getRequestedFor(urlEqualTo("/external/message")));
}
}
이렇게 정의해 두면 해당 URL로 요청이 들어왔을 때 WireMock 서버가 정의된 응답을 대신 내려준다.
실제 외부 서버를 호출하지 않고도 성공 / 실패 / 예외 상황을 자유롭게 만들어 테스트할 수 있었고, 이번 장애 케이스도 동일한 방식으로 재현하여 로직을 확인할 수 있었다.
WireMock을 사용하면서 주의해야 할 점도 있었다.
하나의 JSON 파일에는 하나의 요청/응답 스펙만 정의해야 하며, 여러 개의 JSON 파일을 mappings 폴더에 두면 WireMock이 요청 내용에 맞는 스펙을 찾아 자동으로 매칭하여 응답을 내려준다.
또한 동일한 URL을 사용하는 경우라도 Query Parameter, Header, Body 등의 조건을 함께 정의할 수 있어서 여러 케이스를 나누어 설정할 수 있다.
예를 들어 같은 요청 경로라도 Query Parameter 값이 다른 경우 더 구체적인 조건이 정의된 스펙이 우선적으로 매칭되도록 설정할 수 있어서 테스트 케이스를 세밀하게 제어할 수 있었다.
6. 사용하면서 느낀 점
WireMock을 사용해 보면서 외부 서버에 종속된 대부분의 테스트 케이스를 시험 환경에서 재현할 수 있을 것이라는 기대가 들었다.
지금은 비교적 작은 서비스를 운영하면서 적용해 본 수준이지만, 규모가 큰 프로젝트에서도 충분히 활용할 수 있는 도구라는 느낌을 받았다.
물론 요청과 응답 스펙을 정의해야 하는 부분이 있기 때문에 초기에는 개발자의 학습이 필요한 부분도 있다. 하지만 최근에는 대부분의 스펙 정의를 AI의 도움을 받아 빠르게 작성할 수 있어서 생각보다 진입 장벽이 크지는 않을 것 같았다.
다만 폐쇄망 환경에서 프로젝트를 진행하는 경우에는 Mock 서버를 어떻게 구성하고 관리할 것인지에 대한 고민이 생겼다.
단순히 테스트 용도로 사용하는 수준을 넘어서, 내부에서 Mock 서버를 쉽게 생성하고 관리할 수 있는 Mock Server 관리용 서비스를 따로 만들어 두면 좋지 않을까 하는 생각도 들었다.
예를 들어 서비스 내에서 API 스펙을 정의하고 Mock 서버를 자동으로 생성하여 테스트할 수 있는 구조를 만든다면 대규모 프로젝트에서도 유용하게 사용할 수 있을 것 같다.
또 한 가지 아쉬웠던 점은 WireMock이 실제 요청을 가로채서 응답하는 구조가 아니라, 호출하는 URL 자체를 Mock 서버 주소로 변경해야 한다는 점이었다.
이 부분은 테스트 환경 구성에 따라 번거로울 수 있어서, 프록시 방식이나 MockServer와 같은 다른 도구를 함께 사용하는 방법도 추후에 고민해 볼 필요가 있을 것 같다.
- 예제 코드 : https://github.com/AngryPig123/wiremock
- resources/static/mappings : 해당 위치에
standalone방식에 사용되는 예제 코드 포함.
- resources/static/mappings : 해당 위치에