From b1d6b81eb1035cc99ae03727ccce70152a472944 Mon Sep 17 00:00:00 2001 From: Geonwoo Park Date: Mon, 14 Sep 2026 02:30:06 +0900 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20#253=20Outbox=20=EC=A1=B0=EA=B1=B4?= =?UTF-8?q?=EB=B6=80=20=EB=B9=88=20=EB=93=B1=EB=A1=9D=20=EC=88=9C=EC=84=9C?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Firebase 활성화 속성을 기준으로 Outbox 전송 빈을 등록해 구성 처리 순서 의존성을 제거 - Firebase와 Outbox 폴링이 모두 활성화된 경우에만 스케줄러를 등록 - 테스트 프로필의 Firebase Mock 구성을 분리하고 조건 조합 회귀 테스트를 추가 - 검증: gradlew.bat check --quiet --rerun-tasks 통과 --- .../global/config/FirebaseConfig.java | 2 + .../outbox/config/OutboxFirebaseConfig.java | 12 ++-- .../outbox/config/OutboxSchedulingConfig.java | 8 +-- .../OutboxFirebaseConditionContextTest.java | 64 ++++++++++++++++--- .../OutboxPublisherIntegrationTest.java | 3 +- 5 files changed, 69 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/beachcheck/global/config/FirebaseConfig.java b/src/main/java/com/beachcheck/global/config/FirebaseConfig.java index 9732b658..064fec5b 100644 --- a/src/main/java/com/beachcheck/global/config/FirebaseConfig.java +++ b/src/main/java/com/beachcheck/global/config/FirebaseConfig.java @@ -16,6 +16,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.core.io.ClassPathResource; import org.springframework.util.StringUtils; @@ -34,6 +35,7 @@ * 자격증명을 탐색한다. */ @Configuration +@Profile("!test") @ConditionalOnProperty( prefix = "app.firebase", name = "enabled", diff --git a/src/main/java/com/beachcheck/outbox/config/OutboxFirebaseConfig.java b/src/main/java/com/beachcheck/outbox/config/OutboxFirebaseConfig.java index e4f59fd3..d5c81d5d 100644 --- a/src/main/java/com/beachcheck/outbox/config/OutboxFirebaseConfig.java +++ b/src/main/java/com/beachcheck/outbox/config/OutboxFirebaseConfig.java @@ -7,22 +7,26 @@ import com.beachcheck.outbox.tracing.OutboxTracing; import com.google.firebase.messaging.FirebaseMessaging; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** - * Why: FirebaseMessaging 빈이 있을 때만 Outbox 전송 관련 빈을 한곳에서 조건부 등록하기 위해. + * Why: Firebase가 활성화됐을 때만 Outbox 전송 관련 빈을 한곳에서 조건부 등록하기 위해. * *

Policy: * *

*/ @Configuration(proxyBeanMethods = false) -@ConditionalOnBean(FirebaseMessaging.class) +@ConditionalOnProperty( + prefix = "app.firebase", + name = "enabled", + havingValue = "true", + matchIfMissing = false) public class OutboxFirebaseConfig { @Bean diff --git a/src/main/java/com/beachcheck/outbox/config/OutboxSchedulingConfig.java b/src/main/java/com/beachcheck/outbox/config/OutboxSchedulingConfig.java index 0770c4fd..1058b210 100644 --- a/src/main/java/com/beachcheck/outbox/config/OutboxSchedulingConfig.java +++ b/src/main/java/com/beachcheck/outbox/config/OutboxSchedulingConfig.java @@ -3,7 +3,6 @@ import com.beachcheck.outbox.service.OutboxPublisher; import java.util.UUID; import org.slf4j.MDC; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.Scheduled; @@ -15,15 +14,14 @@ *

Policy: * *

*/ @Configuration -@ConditionalOnBean(OutboxPublisher.class) @ConditionalOnProperty( - prefix = "app.outbox.polling", - name = "enabled", + prefix = "app", + name = {"firebase.enabled", "outbox.polling.enabled"}, havingValue = "true", matchIfMissing = false) public class OutboxSchedulingConfig { diff --git a/src/test/java/com/beachcheck/outbox/config/OutboxFirebaseConditionContextTest.java b/src/test/java/com/beachcheck/outbox/config/OutboxFirebaseConditionContextTest.java index 1f1193d2..38f789b3 100644 --- a/src/test/java/com/beachcheck/outbox/config/OutboxFirebaseConditionContextTest.java +++ b/src/test/java/com/beachcheck/outbox/config/OutboxFirebaseConditionContextTest.java @@ -12,6 +12,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -27,21 +28,24 @@ class OutboxFirebaseConditionContextTest { .withBean(OutboxTracing.class, () -> mock(OutboxTracing.class)); @Test - @DisplayName("FirebaseMessaging 빈이 없으면 Outbox 관련 빈 없이도 컨텍스트가 정상 기동된다") - void whenFirebaseMessagingMissing_thenContextLoadsWithoutOutboxBeans() { - contextRunner.run( - context -> { - assertThat(context).hasNotFailed(); - assertThat(context).doesNotHaveBean(OutboxEventDispatcher.class); - assertThat(context).doesNotHaveBean(OutboxPublisher.class); - assertThat(context).doesNotHaveBean(OutboxSchedulingConfig.class); - }); + @DisplayName("Firebase가 비활성화되면 Outbox 관련 빈 없이도 컨텍스트가 정상 기동된다") + void whenFirebaseDisabled_thenContextLoadsWithoutOutboxBeans() { + contextRunner + .withPropertyValues("app.firebase.enabled=false") + .run( + context -> { + assertThat(context).hasNotFailed(); + assertThat(context).doesNotHaveBean(OutboxEventDispatcher.class); + assertThat(context).doesNotHaveBean(OutboxPublisher.class); + assertThat(context).doesNotHaveBean(OutboxSchedulingConfig.class); + }); } @Test @DisplayName("FirebaseMessaging 빈이 있으면 Outbox 관련 빈을 함께 등록한다") void whenFirebaseMessagingPresent_thenRegisterOutboxBeans() { contextRunner + .withPropertyValues("app.firebase.enabled=true") .withBean(FirebaseMessaging.class, () -> mock(FirebaseMessaging.class)) .run( context -> { @@ -52,7 +56,47 @@ void whenFirebaseMessagingPresent_thenRegisterOutboxBeans() { }); } + @Test + @DisplayName("Outbox 폴링이 비활성화되면 전송 빈은 유지하고 스케줄러만 등록하지 않는다") + void whenOutboxPollingDisabled_thenRegisterDispatchBeansWithoutScheduler() { + contextRunner + .withPropertyValues("app.firebase.enabled=true", "app.outbox.polling.enabled=false") + .withBean(FirebaseMessaging.class, () -> mock(FirebaseMessaging.class)) + .run( + context -> { + assertThat(context).hasNotFailed(); + assertThat(context).hasSingleBean(OutboxEventDispatcher.class); + assertThat(context).hasSingleBean(OutboxPublisher.class); + assertThat(context).doesNotHaveBean(OutboxSchedulingConfig.class); + }); + } + + @Test + @DisplayName("Firebase 활성화 시 FirebaseMessaging 설정 처리 순서와 관계없이 Outbox 빈을 등록한다") + void whenFirebaseEnabled_thenRegisterOutboxBeansRegardlessOfConfigurationOrder() { + contextRunner + .withPropertyValues("app.firebase.enabled=true") + .withUserConfiguration(FirebaseMessagingConfig.class) + .run( + context -> { + assertThat(context).hasNotFailed(); + assertThat(context).hasSingleBean(FirebaseMessaging.class); + assertThat(context).hasSingleBean(OutboxEventDispatcher.class); + assertThat(context).hasSingleBean(OutboxPublisher.class); + assertThat(context).hasSingleBean(OutboxSchedulingConfig.class); + }); + } + @Configuration(proxyBeanMethods = false) - @Import({OutboxFirebaseConfig.class, OutboxSchedulingConfig.class}) + @Import({OutboxSchedulingConfig.class, OutboxFirebaseConfig.class}) static class OutboxBeansConfig {} + + @Configuration(proxyBeanMethods = false) + static class FirebaseMessagingConfig { + + @Bean + FirebaseMessaging firebaseMessaging() { + return mock(FirebaseMessaging.class); + } + } } diff --git a/src/test/java/com/beachcheck/outbox/integration/OutboxPublisherIntegrationTest.java b/src/test/java/com/beachcheck/outbox/integration/OutboxPublisherIntegrationTest.java index 631c6b33..f6200b31 100644 --- a/src/test/java/com/beachcheck/outbox/integration/OutboxPublisherIntegrationTest.java +++ b/src/test/java/com/beachcheck/outbox/integration/OutboxPublisherIntegrationTest.java @@ -63,7 +63,8 @@ .NOT_SUPPORTED) // OutboxPublisher의 REQUIRES_NEW 트랜잭션과 격리하여 테스트 간 트랜잭션 롤백이 영향을 주지 않도록 함. // 대신 @BeforeEach에서 deleteAll()로 DB 상태 초기화 @Import(TracingTestConfiguration.class) -@TestPropertySource(properties = "management.tracing.sampling.probability=1.0") +@TestPropertySource( + properties = {"management.tracing.sampling.probability=1.0", "app.firebase.enabled=true"}) class OutboxPublisherIntegrationTest extends IntegrationTest { private static final String PRODUCER_TRACE_ID = "4bf92f3577b34da6a3ce929d0e0e4736"; From bad9bf689804ef2ae96659b58602e2ab455ae3bb Mon Sep 17 00:00:00 2001 From: Geonwoo Park Date: Mon, 14 Sep 2026 02:41:00 +0900 Subject: [PATCH 2/4] =?UTF-8?q?docs:=20#253=20Outbox=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=84=A4=EC=A0=95=20=EC=9D=98=EB=8F=84=20=EB=AA=85?= =?UTF-8?q?=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 테스트 스케줄러 자동 실행이 DB 검증을 비결정적으로 만드는 이유를 설명 - FirebaseMessaging Mock과 OutboxPublisher 수동 호출 방식을 명시 - 검증: staged diff 및 whitespace 검사 통과 --- src/test/resources/application-test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index 9aed543d..15f47825 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -70,7 +70,9 @@ app: outbox: polling: - enabled: false # Outbox 폴링 스케줄러 비활성화 (수동 호출로 테스트) + # @SpringBootTest 기반 Outbox 통합 테스트에서 처리 시점을 직접 제어하기 위해 스케줄러 자동 실행을 비활성화한다. + # Outbox 통합 테스트는 FirebaseMessaging Mock을 사용하고 OutboxPublisher를 직접 호출한다. + enabled: false logging: level: From 1e4f0474dea297596bcee11a3060bcb9dc5a4c17 Mon Sep 17 00:00:00 2001 From: Geonwoo Park Date: Mon, 14 Sep 2026 03:04:13 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20#253=20Firebase=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=9A=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FirebaseConfig의 test 프로필 제외 조건을 제거해 enabled 속성만 등록 기준으로 유지 - 실제 Firebase와 Outbox 운영 설정을 함께 로드하는 순서 회귀 테스트로 보강 - OutboxPublisher 통합 테스트는 Firebase를 켜지 않고 테스트 전용 Outbox 빈과 Mock으로 격리 - 검증: gradlew.bat check --quiet --rerun-tasks 통과 --- .../global/config/FirebaseConfig.java | 2 - .../OutboxFirebaseConditionContextTest.java | 74 +++++++++++++------ .../OutboxPublisherIntegrationTest.java | 33 ++++++++- 3 files changed, 80 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/beachcheck/global/config/FirebaseConfig.java b/src/main/java/com/beachcheck/global/config/FirebaseConfig.java index 064fec5b..9732b658 100644 --- a/src/main/java/com/beachcheck/global/config/FirebaseConfig.java +++ b/src/main/java/com/beachcheck/global/config/FirebaseConfig.java @@ -16,7 +16,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; import org.springframework.core.io.ClassPathResource; import org.springframework.util.StringUtils; @@ -35,7 +34,6 @@ * 자격증명을 탐색한다. */ @Configuration -@Profile("!test") @ConditionalOnProperty( prefix = "app.firebase", name = "enabled", diff --git a/src/test/java/com/beachcheck/outbox/config/OutboxFirebaseConditionContextTest.java b/src/test/java/com/beachcheck/outbox/config/OutboxFirebaseConditionContextTest.java index 38f789b3..4589dc38 100644 --- a/src/test/java/com/beachcheck/outbox/config/OutboxFirebaseConditionContextTest.java +++ b/src/test/java/com/beachcheck/outbox/config/OutboxFirebaseConditionContextTest.java @@ -1,18 +1,25 @@ package com.beachcheck.outbox.config; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import com.beachcheck.global.config.FirebaseConfig; import com.beachcheck.notification.repository.NotificationRepository; import com.beachcheck.outbox.repository.OutboxEventRepository; import com.beachcheck.outbox.service.OutboxEventDispatcher; import com.beachcheck.outbox.service.OutboxPublisher; import com.beachcheck.outbox.tracing.OutboxTracing; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; import com.google.firebase.messaging.FirebaseMessaging; +import java.io.InputStream; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -21,7 +28,6 @@ class OutboxFirebaseConditionContextTest { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withUserConfiguration(OutboxBeansConfig.class) .withPropertyValues("app.outbox.polling.enabled=true", "app.outbox.polling.batch-size=10") .withBean(OutboxEventRepository.class, () -> mock(OutboxEventRepository.class)) .withBean(NotificationRepository.class, () -> mock(NotificationRepository.class)) @@ -31,6 +37,7 @@ class OutboxFirebaseConditionContextTest { @DisplayName("Firebase가 비활성화되면 Outbox 관련 빈 없이도 컨텍스트가 정상 기동된다") void whenFirebaseDisabled_thenContextLoadsWithoutOutboxBeans() { contextRunner + .withUserConfiguration(OutboxBeansConfig.class) .withPropertyValues("app.firebase.enabled=false") .run( context -> { @@ -42,9 +49,10 @@ void whenFirebaseDisabled_thenContextLoadsWithoutOutboxBeans() { } @Test - @DisplayName("FirebaseMessaging 빈이 있으면 Outbox 관련 빈을 함께 등록한다") - void whenFirebaseMessagingPresent_thenRegisterOutboxBeans() { + @DisplayName("Firebase가 활성화되고 FirebaseMessaging 의존성이 있으면 Outbox 관련 빈을 등록한다") + void whenFirebaseEnabledWithMessaging_thenRegisterOutboxBeans() { contextRunner + .withUserConfiguration(OutboxBeansConfig.class) .withPropertyValues("app.firebase.enabled=true") .withBean(FirebaseMessaging.class, () -> mock(FirebaseMessaging.class)) .run( @@ -60,6 +68,7 @@ void whenFirebaseMessagingPresent_thenRegisterOutboxBeans() { @DisplayName("Outbox 폴링이 비활성화되면 전송 빈은 유지하고 스케줄러만 등록하지 않는다") void whenOutboxPollingDisabled_thenRegisterDispatchBeansWithoutScheduler() { contextRunner + .withUserConfiguration(OutboxBeansConfig.class) .withPropertyValues("app.firebase.enabled=true", "app.outbox.polling.enabled=false") .withBean(FirebaseMessaging.class, () -> mock(FirebaseMessaging.class)) .run( @@ -72,19 +81,41 @@ void whenOutboxPollingDisabled_thenRegisterDispatchBeansWithoutScheduler() { } @Test - @DisplayName("Firebase 활성화 시 FirebaseMessaging 설정 처리 순서와 관계없이 Outbox 빈을 등록한다") - void whenFirebaseEnabled_thenRegisterOutboxBeansRegardlessOfConfigurationOrder() { - contextRunner - .withPropertyValues("app.firebase.enabled=true") - .withUserConfiguration(FirebaseMessagingConfig.class) - .run( - context -> { - assertThat(context).hasNotFailed(); - assertThat(context).hasSingleBean(FirebaseMessaging.class); - assertThat(context).hasSingleBean(OutboxEventDispatcher.class); - assertThat(context).hasSingleBean(OutboxPublisher.class); - assertThat(context).hasSingleBean(OutboxSchedulingConfig.class); - }); + @DisplayName("운영 Firebase와 Outbox 설정을 함께 로드하면 처리 순서와 관계없이 관련 빈을 등록한다") + void whenProductionConfigurationsLoad_thenRegisterFirebaseAndOutboxBeans() { + FirebaseApp firebaseApp = mock(FirebaseApp.class); + FirebaseMessaging firebaseMessaging = mock(FirebaseMessaging.class); + + try (MockedStatic googleCredentials = mockStatic(GoogleCredentials.class); + MockedStatic firebaseApps = mockStatic(FirebaseApp.class); + MockedStatic firebaseMessagingInstances = + mockStatic(FirebaseMessaging.class)) { + googleCredentials + .when(() -> GoogleCredentials.fromStream(any(InputStream.class))) + .thenReturn(mock(GoogleCredentials.class)); + firebaseApps.when(FirebaseApp::getApps).thenReturn(java.util.List.of()); + firebaseApps + .when(() -> FirebaseApp.initializeApp(any(FirebaseOptions.class))) + .thenReturn(firebaseApp); + firebaseMessagingInstances + .when(() -> FirebaseMessaging.getInstance(firebaseApp)) + .thenReturn(firebaseMessaging); + + contextRunner + .withInitializer(context -> context.getEnvironment().setActiveProfiles("test")) + .withUserConfiguration(ProductionBeansConfig.class) + .withPropertyValues( + "app.firebase.enabled=true", "app.firebase.credentials-json-base64=e30=") + .run( + context -> { + assertThat(context).hasNotFailed(); + assertThat(context).hasSingleBean(FirebaseApp.class); + assertThat(context).hasSingleBean(FirebaseMessaging.class); + assertThat(context).hasSingleBean(OutboxEventDispatcher.class); + assertThat(context).hasSingleBean(OutboxPublisher.class); + assertThat(context).hasSingleBean(OutboxSchedulingConfig.class); + }); + } } @Configuration(proxyBeanMethods = false) @@ -92,11 +123,6 @@ void whenFirebaseEnabled_thenRegisterOutboxBeansRegardlessOfConfigurationOrder() static class OutboxBeansConfig {} @Configuration(proxyBeanMethods = false) - static class FirebaseMessagingConfig { - - @Bean - FirebaseMessaging firebaseMessaging() { - return mock(FirebaseMessaging.class); - } - } + @Import({OutboxSchedulingConfig.class, OutboxFirebaseConfig.class, FirebaseConfig.class}) + static class ProductionBeansConfig {} } diff --git a/src/test/java/com/beachcheck/outbox/integration/OutboxPublisherIntegrationTest.java b/src/test/java/com/beachcheck/outbox/integration/OutboxPublisherIntegrationTest.java index f6200b31..d7202956 100644 --- a/src/test/java/com/beachcheck/outbox/integration/OutboxPublisherIntegrationTest.java +++ b/src/test/java/com/beachcheck/outbox/integration/OutboxPublisherIntegrationTest.java @@ -19,7 +19,9 @@ import com.beachcheck.notification.repository.NotificationRepository; import com.beachcheck.outbox.domain.OutboxEvent; import com.beachcheck.outbox.repository.OutboxEventRepository; +import com.beachcheck.outbox.service.OutboxEventDispatcher; import com.beachcheck.outbox.service.OutboxPublisher; +import com.beachcheck.outbox.tracing.OutboxTracing; import com.beachcheck.support.base.IntegrationTest; import com.beachcheck.support.tracing.RecordingSpanExporter; import com.beachcheck.support.tracing.TracingTestConfiguration; @@ -39,6 +41,9 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.test.context.TestPropertySource; import org.springframework.transaction.annotation.Propagation; @@ -62,9 +67,8 @@ Propagation .NOT_SUPPORTED) // OutboxPublisher의 REQUIRES_NEW 트랜잭션과 격리하여 테스트 간 트랜잭션 롤백이 영향을 주지 않도록 함. // 대신 @BeforeEach에서 deleteAll()로 DB 상태 초기화 -@Import(TracingTestConfiguration.class) -@TestPropertySource( - properties = {"management.tracing.sampling.probability=1.0", "app.firebase.enabled=true"}) +@Import({TracingTestConfiguration.class, OutboxPublisherIntegrationTest.OutboxTestConfig.class}) +@TestPropertySource(properties = "management.tracing.sampling.probability=1.0") class OutboxPublisherIntegrationTest extends IntegrationTest { private static final String PRODUCER_TRACE_ID = "4bf92f3577b34da6a3ce929d0e0e4736"; @@ -86,6 +90,29 @@ class OutboxPublisherIntegrationTest extends IntegrationTest { @Autowired private SdkTracerProvider tracerProvider; + @TestConfiguration(proxyBeanMethods = false) + static class OutboxTestConfig { + + @Bean + OutboxEventDispatcher outboxEventDispatcher( + OutboxEventRepository outboxEventRepository, + NotificationRepository notificationRepository, + FirebaseMessaging firebaseMessaging) { + return new OutboxEventDispatcher( + outboxEventRepository, notificationRepository, firebaseMessaging); + } + + @Bean + OutboxPublisher outboxPublisher( + OutboxEventRepository outboxEventRepository, + OutboxEventDispatcher outboxEventDispatcher, + OutboxTracing outboxTracing, + @Value("${app.outbox.polling.batch-size:10}") int batchSize) { + return new OutboxPublisher( + outboxEventRepository, outboxEventDispatcher, outboxTracing, batchSize); + } + } + @BeforeEach void setUp() throws FirebaseMessagingException { flushSpans(tracerProvider); From a56de0107a6aef39e8a77e3f99009f22675c7837 Mon Sep 17 00:00:00 2001 From: Geonwoo Park Date: Mon, 14 Sep 2026 03:10:02 +0900 Subject: [PATCH 4/4] =?UTF-8?q?docs:=20#253=20Firebase=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=84=A4=EC=A0=95=20=EC=84=A4=EB=AA=85=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FirebaseConfig가 test 프로필이 아닌 enabled 속성으로 비활성화됨을 명시 - FirebaseMessaging Mock의 선택 및 동작 설정 방식을 현재 테스트 구조에 맞게 수정 - 검증: gradlew.bat check --quiet --rerun-tasks 통과 --- .../beachcheck/support/config/FirebaseTestConfig.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/beachcheck/support/config/FirebaseTestConfig.java b/src/test/java/com/beachcheck/support/config/FirebaseTestConfig.java index 6725ca85..f856bc76 100644 --- a/src/test/java/com/beachcheck/support/config/FirebaseTestConfig.java +++ b/src/test/java/com/beachcheck/support/config/FirebaseTestConfig.java @@ -16,8 +16,8 @@ * *
    *
  • @TestConfiguration으로 테스트 환경에만 적용 - *
  • @Primary로 FirebaseConfig의 실제 빈보다 우선순위 높게 설정 (실제 FirebaseConfig는 test 환경에서 비활성화되어 있지만 명시적 - * 우선순위 지정) + *
  • 기본 test 프로필의 app.firebase.enabled=false로 실제 FirebaseConfig를 비활성화하고 Mock 빈을 사용 + *
  • @Primary로 테스트용 FirebaseMessaging 의존성을 명확하게 선택 *
  • ApiTest, IntegrationTest 베이스 클래스에 @Import로 자동 적용 *
* @@ -36,9 +36,8 @@ public class FirebaseTestConfig { * *
    *
  • Mockito.mock()으로 Mock 객체 생성 - *
  • @Primary로 실제 FirebaseConfig보다 우선순위 높게 설정 (test 환경에서 FirebaseConfig는 비활성화되어 있지만 명시적 우선순위 - * 지정) - *
  • 개별 테스트에서 @MockBean으로 추가 설정 가능 (예: OutboxPublisherIntegrationTest) + *
  • 기본 test 프로필에서는 app.firebase.enabled=false이므로 실제 FirebaseConfig를 등록하지 않음 + *
  • 개별 테스트에서 반환값과 예외 등 Mock 동작을 추가 설정 가능 *
*/ @Bean