Java Spring 기반에서 Mock Server와 AOP를 결합한 테스트 전략
Java Spring 기반에서 Mock Server와 AOP를 결합한 테스트 전략을 실제 코드 예제와 함께 설명해 드리겠습니다. 아래는 외부 결제 API를 호출하는 서비스를 예제로 설정한 것입니다.
---
🌐 시나리오 설정
PaymentService는 외부 결제 시스템(예: KakaoPay, Toss 등)에 HTTP 요청을 보냄.
실제 호출 대신, 테스트 시에는 Mock Server로 우회하고,
그 우회를 AOP로 처리하여 코드에 변경 없이 테스트 환경을 구성함.
---
1️⃣ 외부 API 인터페이스
public interface PaymentApiClient {
PaymentResponse requestPayment(PaymentRequest request);
}
---
2️⃣ 실제 API 호출 구현 (RestTemplate 예시)
@Service
@Profile("prod")
public class RealPaymentApiClient implements PaymentApiClient {
private final RestTemplate restTemplate = new RestTemplate();
@Override
public PaymentResponse requestPayment(PaymentRequest request) {
ResponseEntity<PaymentResponse> response = restTemplate.postForEntity(
"https://api.payment.com/pay",
request,
PaymentResponse.class
);
return response.getBody();
}
}
---
3️⃣ 테스트용 Mock Server 설정 (예: WireMock)
✅ 설정 (JUnit 5 기반)
@ExtendWith(SpringExtension.class)
@SpringBootTest
@ActiveProfiles("test")
public class PaymentServiceTest {
static WireMockServer wireMockServer;
@BeforeAll
static void setupMockServer() {
wireMockServer = new WireMockServer(8089);
wireMockServer.start();
WireMock.configureFor("localhost", 8089);
WireMock.stubFor(post(urlEqualTo("/pay"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody("{ \"status\": \"SUCCESS\", \"message\": \"결제 완료\" }")));
}
@AfterAll
static void teardown() {
wireMockServer.stop();
}
@Autowired
PaymentService paymentService;
@Test
public void testMockedPayment() {
PaymentResponse response = paymentService.pay(new PaymentRequest(...));
assertEquals("SUCCESS", response.getStatus());
}
}
---
4️⃣ 테스트용 Mock API 클라이언트 구현
@Service
@Profile("test")
public class MockPaymentApiClient implements PaymentApiClient {
private final RestTemplate restTemplate = new RestTemplate();
@Override
public PaymentResponse requestPayment(PaymentRequest request) {
// WireMock이 8089 포트에서 mock 응답 제공
return restTemplate.postForObject("http://localhost:8089/pay", request, PaymentResponse.class);
}
}
---
5️⃣ AOP로 동적 우회 (고급 전략)
✅ 커스텀 어노테이션 정의
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseMockServer {
}
✅ AOP 구현
@Aspect
@Component
@Profile("test")
public class PaymentMockAspect {
@Around("@annotation(UseMockServer)")
public Object interceptWithMock(ProceedingJoinPoint joinPoint) throws Throwable {
// 필요 시 조건 추가 가능
System.out.println("🔁 Mock 서버로 요청 우회 중");
return joinPoint.proceed();
}
}
✅ 테스트 대상 서비스
@Service
public class PaymentService {
private final PaymentApiClient paymentApiClient;
public PaymentService(PaymentApiClient paymentApiClient) {
this.paymentApiClient = paymentApiClient;
}
@UseMockServer
public PaymentResponse pay(PaymentRequest request) {
return paymentApiClient.requestPayment(request);
}
}
---
✅ 정리
기술 사용 이유 이점
Mock Server (WireMock) 외부 API를 실제 호출 없이 모방 테스트 안정성, 속도, 제어력 향상
AOP 테스트 상황에서 실행 흐름 감시 / 제어 코드 침투 없이 mock 로직 주입 가능
@Profile 환경별 Bean 분리 운영과 테스트 분리
---
🧠 추가 팁
@Profile 을 적극 활용해, test 환경에서는 mock, prod 환경에서는 실제 서비스 자동 사용.
Spring Cloud Contract 등으로 WireMock 자동화 가능.
RestTemplate 대신 WebClient 사용 시에도 유사 구조로 적용 가능.
---
원하시면 Gradle 세팅, 전체 프로젝트 구조, 또는 Spring Cloud Contract 기반 테스트 예제도 이어서 제공해 드릴 수 있어요. 어떤 방향이 더 궁금하신가요?