현재 진행하는 프로젝트에서 결제 업무를 위해서 외부 API인 토스 API를 호출하고 있다.
결제 파트의 주요 로직들을 통합테스트 하려는데,
이때 외부 API를 실제로 호출할지 아니면 Mocking하는게 좋을지에 대한 고민이 있었다.
외부 API 호출 여부에 대한 결정은 테스트의 목적과 외부 API에 대한 의존성을 바탕으로 고려되어야 할 것이다.
1️⃣ 외부 API를 실제 호출하는 경우
외부 API를 호출하면 실제 연동 시나리오를 검증할 수 있다는 장점이 있다.
인증 포맷, 인증 흐름 등 외부 시스템과의 실제 통신 과정을 그대로 확인할 수 있으며,
추후 외부 API에 변경이 있다면 빠르게 감지할 수 있다.
반면에, 외부 API가 불안정한 경우, 프로그램 로직과는 무관하게 테스트가 자주 실패되기 때문에 테스트가 일관되지 않을 수 있다.
실제로 현재 프로젝트에서 사용하는 토스 API의 샌드박스는 가끔 불안정한 문제가 있었고,
이는 프론트에 SDK를 연동하여 직접 호출하는 방식으로 구조를 변경해 해결할 수 있었다.
그러나 통합 테스트는 백엔드 위주로 동작하기 때문에
이런 불안정성보다는 우리 프로그램 흐름 자체에 집중하기 위하여 외부 API를 호출하지 않는 방식을 선택했다.
또한 외부 API 호출 시, 실제 HTTP 통신과 인증 과정이 포함되기 때문에 테스트 속도가 느려질 수 있다.
이 부분은 테스트의 목적에 따라 판단하여야 한다.
예를 들어 안정성 검증이 필요한 경우에는 실제 호출이, 빠른 피드백이 중요한 경우에는 Mocking이 더 적합할 수 있다.
2️⃣ 외부 API를 mocking하는 경우
외부 시스템에 의존하지 않기 때문에 빠르고 안정적인 테스트가 가능하다.
실패나 응답 지연 상황도 의도적으로 만들 수 있기 때문에 예외상황 테스트를 하기에도 용이하며,
환경에 따라 테스트가 달라지지 않아서 재현하기에도 좋다.
다만, 외부 API에 변경이 있다면 이를 감지할 수 없기 때문에 실제 연동과는 다를 수 있다.
3️⃣ 결론
통합 테스트에서는 외부 API를 Mocking하고, E2E 테스트에서는 실제 API를 호출하는 방식을 선택했다.
현재 진행하는 통합테스트에서는 외부 API는 가능하면 Mock 처리하여 테스트의 속도와 안정성을 확보하고자 하였다.
다만 실제 인증 흐름을 검증하거나, 외부 api 변경에 대한 대응이 필요한 경우처럼 실제 연동을 확인해야 할 때에는
별도의 E2E 환경에서 실제 호출을 수행하는 테스트를 진행하기로 하였다.
또한 Mock 사용 시 실제 응답 스펙과 다른게 걱정스럽다면, Pact 프레임워크를 사용하여 계약 기반 테스트를 도입할 수 있다.
4️⃣ Spring 기반 통합 테스트 구성
현재 프로젝트에서는 외부 API 호출을 Mocking하는 방식으로 통합 테스트를 구성하고 있습니다.
@SpringBootTest
@AutoConfigureMockMvc
@Testcontainers
class PaymentIntegrationTest {
@Container
static final MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0")
.withDatabaseName("testdb")
.withUsername("testuser")
.withPassword("testpass");
@DynamicPropertySource
static void overrideProps(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", mysql::getJdbcUrl);
registry.add("spring.datasource.username", mysql::getUsername);
registry.add("spring.datasource.password", mysql::getPassword);
}
@Autowired MockMvc mockMvc;
@Autowired ObjectMapper objectMapper;
@MockBean IdempotentRestClient idempotentRestClient; // 외부 API mock
@SpyBean EmailNotificationService emailNotificationService; // 내부 서비스 spy
static String accessToken;
@BeforeAll
static void setup(@Autowired MockMvc mockMvc, @Autowired ObjectMapper objectMapper) throws Exception {
// 회원가입 및 로그인 → accessToken 발급
mockMvc.perform(post("/api/v1/auth/sellers/signup")...);
var result = mockMvc.perform(post("/api/v1/auth/login")...);
accessToken = ... // 응답에서 JWT 추출
}
@Test
@DisplayName("결제 승인 성공 시 Payment/Order 저장 및 알림 전송")
void confirmMultiPayment_success() throws Exception {
// given
Order order1 = orderRepository.save(...);
TossPaymentConfirmResponse tossMock = new TossPaymentConfirmResponse();
when(idempotentRestClient.postForIdempotent(...)).thenReturn(tossMock);
// when
mockMvc.perform(post("/api/v1/payments/multi")
.header("Authorization", "Bearer " + accessToken)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk());
// then
verify(emailNotificationService, times(1)).send(any());
}
}
@SpringBootTest와 @TestContainers를 활용하여 Docker 기반의 MySQL을 통해 실제 DB 환경을 구성합니다.
테스트 중 실제 HTTP 요청 흐름은 MockMvc로 시뮬레이션 합니다.
외부 결제 API인 TOSS 호출은 @MockBean으로 대체하여 네트워크 의존성을 제거하였고,
내부 알림 서비스는 @SpyBean을 사용하여 실제 메서드 호출 여부를 검증합니다.
또한 테스트 초기에는 회원가입과 로그인 요청을 수행하여 accessToken을 발급받고,
이후의 인증 요청에 활용함으로써 인증 기반 흐름까지 실제 서비스처럼 구성되어 있습니다.
이처럼 외부 API 의존성은 제거하되, 전체 결제 흐름은 실제 서비스 수준에서 검증할 수 있도록 통합 테스트를 구성하였습니다.

'👩🏻💻Project' 카테고리의 다른 글
| 🚝열차 좌석 예매에서의 동시성 제어 : Redis 기반 분산락 활용 (5) | 2025.08.08 |
|---|---|
| 🤔 외부 API 어떤 방식으로 호출할지, RestTemplate vs WebClient (2) | 2025.07.04 |
| ✅Repository의 테스트 환경에 대한 단위테스트 전략 (1) | 2025.07.03 |
| 🚀[트러블슈팅] Toss API 오류 대응을 위한 커스텀 ErrorHandler 적용 및 재시도 제어 개선 (2) | 2025.07.02 |
| 🔐Spring Security가 적용된 Controller 단위 테스트에서 403 에러 해결 (1) | 2025.07.01 |