4차산업혁명의 일꾼/Spring

Java Spring 기반에서 Mock Server와 AOP를 결합한 테스트 전략

르무엘 2025. 6. 4. 10:17

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 기반 테스트 예제도 이어서 제공해 드릴 수 있어요. 어떤 방향이 더 궁금하신가요?


LIST