스프링 통합 테스트에서 Kafka를 지원하는 방법 (@EmbeddedKafka, @ConditionalOnProperty)

2025. 5. 12. 19:21·Etc
반응형

 

들어가면서

경매 시스템의 입찰 로직에 Kafka 기반 비동기 이벤트 전송 기능을 도입하게 되었다.
입찰 등록 시 Kafka로 이벤트를 발행하고, 이를 Consumer가 받아서 후속 처리하는 구조로 변경하면서,
이벤트 기반 아키텍처의 이점을 실제로 적용하고 검증해보고자 했다.

이후 통합 테스트를 작성하면서 Kafka까지 포함된 end-to-end 테스트를 수행하려고 했고,
이를 위해 spring-kafka-test 라이브러리의 @EmbeddedKafka를 사용하여 내장 브로커 기반 테스트 환경을 구성했다.

 

 

Embedded Kafka   vs   Testcontainers

`Embedded Kafka`는 JVM 안에 Kafka 브로커를 띄우는 방식이라 속도가 빠르고 설정이 간단하지만, 실제 Kafka와 완벽히 동일하지 않다는 단점이 있다. 반면, `Testcontainers`는 Docker 기반의 실제 Kafka 인스턴스를 띄우기 때문에 프로덕션 환경과 거의 동일한 테스트가 가능하지만, 속도가 느리고 Docker 의존성이 있다는 trade-off가 있다.

 

 

 

입찰 도메인의 통합 테스트는 Kafka를 설정하여 통과했는데, 문제가 발생했다.
동일한 API 모듈의 다른 도메인 테스트 코드들이 전부 터지기 시작한 것이다. 원인은 간단했다.

 

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'kafkaProducerConfig'

...

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'spring.embedded.kafka.brokers' in value "${spring.embedded.kafka.brokers}"

...

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'biddingEventProducer'

...

 

KafkaProducerConfig, KafkaConsumerConfig가 언제나 빈으로 등록되던 상태라,
Kafka에 대한 의존이 없는 테스트들도 Kafka 연결을 시도하면서 오류가 났던 것.

 

이에 대한 해결책으로 `@ConditionalOnProperty`를 도입하여,
`event.kafka.enabled: true`일 때만 Kafka 관련 빈이 등록되도록 수정했다.

 

 

@ConditionalOnProperty란?

@Configuration
@ConditionalOnProperty(name = "event.kafka.enabled", havingValue = "true", matchIfMissing = false)
public class KafkaProducerConfig {

	...

스프링부트에서 제공하는 이 어노테이션은 설정 파일에 명시된 프로퍼티 값에 따라 특정 Bean을 등록할지 말지를 결정할 수 있게 해준다.
여기서 `event.kafka.enabled`라는 설정이 "true"일 때만 해당 설정이 적용되고,
`matchIfMissing = false` 덕분에 이 프로퍼티가 아예 없으면 등록되지 않도록 만들 수 있다.

즉, Kafka가 필요한 상황에서만 관련 설정이 활성화되도록 제어한 것이다.

 

 

이후 EmbeddedKafka 통합 테스트 환경에서는 `@ConditionalOnProperty` 조건이 무시되는 예상치 못한 이슈까지 겪게 되었다.
Spring Boot
의 테스트 컨텍스트와 조건부 빈 등록 사이의 관계를 이해를 할 수 있었고, 실제 운영환경과 테스트 환경을 분리하고 안전하게 구성하는 설계에 대해 고민하게 되는 계기가 되었던 개발일기이다.

 

 

 

EmbeddedKafka 통합 테스트 환경에서, 왜 @ConditionalOnProperty가 무시되었을까?

 

@SpringJUnitConfig // Embedded Broker가 Test Application Context에 추가됨. -> Broker를 Autowire 해줄 수 있음.
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public abstract class ApiWithKafkaTestSupport extends ApiTestSupport {

    private static String BROKER_ADDRESS;

    @Autowired
    private EmbeddedKafkaBroker broker;

    @DynamicPropertySource
    static void setKafkaProps(DynamicPropertyRegistry registry) {
        registry.add("spring.kafka.producer.bootstrap-servers", () -> BROKER_ADDRESS);
        registry.add("spring.kafka.consumer.bootstrap-servers", () -> BROKER_ADDRESS);
    }

    @BeforeAll
    void setupBroker(@Autowired EmbeddedKafkaBroker broker) {
        BROKER_ADDRESS = broker.getBrokersAsString();
    }
}
@DisplayName("[BiddingApiController 테스트]")
@EmbeddedKafka(partitions = 1, topics = {"bidding-events"})
class BiddingApiControllerTest extends ApiWithKafkaTestSupport {

	...
# test의 application.yml

spring:
  kafka:
    producer:
      bootstrap-servers: ${spring.embedded.kafka.brokers}
    consumer:
      bootstrap-servers: ${spring.embedded.kafka.brokers}
event:
  kafka:
    enabled: false
    
    ...
@Configuration
@ConditionalOnProperty(name = "event.kafka.enabled", havingValue = "true", matchIfMissing = false)
public class KafkaProducerConfig {

	...

 

 

분명 false로 설정했으니 빈이 등록되지 않아야 한다.

그런데 입찰 통합 테스트에서는 카프카가 잘 동작하고 있다!

 

 

 

원인 분석: 테스트에서 조건이 무시되는 이유

 

1. @EmbeddedKafka가 테스트 환경에서 Kafka Broker를 띄움

@EmbeddedKafka(partitions = 1, topics = {"bidding-events"})
  • spring-kafka-test에서 제공하는 애노테이션
  • 테스트 컨텍스트에 `Kafka 브로커를 자동 등록`해줌
  • 이와 함께 EmbeddedKafkaBroker 빈이 생성됨

 

 

2. @DynamicPropertySource로 Kafka 서버 주소를 덮어씀

@DynamicPropertySource
static void setKafkaProps(DynamicPropertyRegistry registry) {
    registry.add("spring.kafka.producer.bootstrap-servers", () -> BROKER_ADDRESS);
    registry.add("spring.kafka.consumer.bootstrap-servers", () -> BROKER_ADDRESS);
}
  • application.yml보다 `우선순위 높은 테스트 프로퍼티를 설정`
  • 그래서 실제 프로퍼티 설정값은 덮어써짐

 

 

3. @SpyBean, @Import, @SpringJUnitConfig의 조합

// BiddingApiControllerTest

@SpyBean
private BiddingEventListener biddingEventListener;
  • @SpyBean은 테스트 시 자동으로 Bean을 등록하거나 감싸줌
  • 이로 인해, `해당 Bean이 의존하는 Kafka 관련 빈들도 자동 주입됨`
  • 이 과정에서 @ConditionalOnProperty의 조건이 무시될 수 있음

 

 

 

마무리

 

1. 실제 운영 환경에서는 `event.kafka.enabled: true`가 반드시 필요하다. 테스트는 편의상 조건을 무시할 수 있지만, 운영에서는 조건부 등록에 의존하는 Kafka 설정값이 적용된다. 

 

2. 테스트 환경에서는 실제 Kafka를 구동하지 말고 EmbeddedKafka를 사용해서 테스트 시간을 아껴주자. 이번 포스팅에서 다뤘던 포인트를 주의하면서.

 

 

 

 

 

반응형
저작자표시 비영리 변경금지 (새창열림)

'Etc' 카테고리의 다른 글

[Git] 원격 저장소와 로컬 저장소 병합 문제 해결 : non-fast-forward  (0) 2025.01.09
ChatGPT로 간단한 웹사이트 만들기 - 3  (0) 2023.04.25
ChatGPT로 간단한 웹사이트 만들기 - 2  (0) 2023.04.25
ChatGPT로 간단한 웹사이트 만들기 - 1  (0) 2023.04.22
IT 개인 블로그 추천 (백엔드 / 알고리즘)  (1) 2022.07.26
'Etc' 카테고리의 다른 글
  • [Git] 원격 저장소와 로컬 저장소 병합 문제 해결 : non-fast-forward
  • ChatGPT로 간단한 웹사이트 만들기 - 3
  • ChatGPT로 간단한 웹사이트 만들기 - 2
  • ChatGPT로 간단한 웹사이트 만들기 - 1
Giken
Giken
𝐒𝐲𝐬𝐭𝐞𝐦.𝐨𝐮𝐭.𝐩𝐫𝐢𝐧𝐭𝐥𝐧("𝐇𝐞𝐥𝐥𝐨 𝐖𝐨𝐫𝐥𝐝!");
  • Giken
    개발자 기켄
    Giken
  • 전체
    오늘
    어제
    • 분류 전체보기 (148)
      • Programming Language (26)
        • C (3)
        • C++ (2)
        • Java (19)
      • Web (4)
      • Database (1)
        • SQL (5)
      • Spring (10)
      • PHP (7)
      • Linux (1)
      • Server (1)
      • Infra (3)
      • Algorithm (74)
        • 백준 (71)
        • 프로그래머스 (0)
      • 프로젝트 (2)
      • Etc (8)
      • 낙서 (5)
  • 블로그 메뉴

    • GitHub
  • 링크

    • GitHub
  • 공지사항

  • 인기 글

  • 태그

    2588
    2753
    프로그래머스
    SQL고득점키트
    C
    DB
    백준
    9498
    평년
    1330
    윤년
    SQL
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Giken
스프링 통합 테스트에서 Kafka를 지원하는 방법 (@EmbeddedKafka, @ConditionalOnProperty)
상단으로

티스토리툴바