Files
llm-wiki/raw/errors/webmvctest-nested-springbootconfiguration-context-pollution-2026-06-01.md

6.7 KiB

title, source_type, status, related_branches, related_projects, tags, created, status_label
title source_type status related_branches related_projects tags created status_label
error / webmvctest-nested-springbootconfiguration-context-pollution-2026-06-01 error-note raw
feature-operational-error-observability-foundation
ca-skeleton
error
ca-skeleton
spring-boot-test
webmvctest
springbootconfiguration
component-scan
mockmvc
testing
2026-06-01 resolved

error: webmvctest-nested-springbootconfiguration-context-pollution-2026-06-01

Layer: raw/errors/ — 실제 발생한 오류 / 막힘 / 트러블슈팅의 원석. canonical 정제 전 raw 후보이며, wiki/blog/ 또는 wiki/troubleshooting/ 직접 생성 근거가 아니다.

Parent / 부모

증상

Phase C2 (envelope meta 객체 + error.category 도입) 구현 후 ./gradlew test 에서 app-bootstrap:test 만 5건 실패. 두 부류:

  1. 신규 EnvelopeMetaContractTest (3건) — 전부 컨텍스트 로드 단계 실패:
    • problemdetails_is_pinned_off()org.springframework.boot.context.properties.bind.BindExceptionIllegalStateException at Assert.java:101
    • 나머지 2건도 DefaultCacheAwareContextLoaderDelegate 컨텍스트 로드 실패.
  2. 기존 OperationalContractRuntimeTest (2건) — Phase C2 이전엔 통과하던 회귀:
    • operationalContractBeans_areWiredIntoTheApplicationContext()AssertionError: [EnvelopeBodyAdvice must be component-scanned ...] Expecting actual not to be empty (즉 EnvelopeBodyAdvice 빈이 컨텍스트에 없음)
    • envelopeAdvice_wrapsRawControllerBody()PathNotFoundException on $.success (응답이 envelope 로 안 감싸짐)

shared-contract / adapter-web / sample-portfolio 의 자체 슬라이스 테스트는 전부 통과 — 문제는 app-bootstrap 컨텍스트에 국한.

조사 단계 / Investigation log

정적 추론으로는 "왜 @RestControllerAdvice 가 컴포넌트 스캔에서 빠지나"가 안 풀려, 격리 실험으로 좁혔다 (working tree 미커밋 상태였으므로 비파괴적 진단 사용):

  1. git stash -u 로 전체 변경 임시 제거 → 원본(c36b764)에서 OperationalContractRuntimeTest 실행 → BUILD SUCCESSFUL. → 내 변경이 회귀 원인 확정. git stash pop 으로 복원.
  2. yaml 2개(application.yml/application-test.yml)만 git stash push -- <files> 로 격리 → 여전히 실패. → config 무관, Java 변경이 원인.
  3. 신규 EnvelopeMetaContractTest.java/tmpmv 한 뒤 OperationalContractRuntimeTest 실행 → BUILD SUCCESSFUL. → EnvelopeMetaContractTest 의 존재 자체가 같은 패키지의 다른 테스트를 오염시킨다고 확정.

근본 원인 / Root cause

신규 EnvelopeMetaContractTestOperationalContractRuntimeTest동일 패키지 (dev.caskeleton.bootstrap.runtime) 에 있으면서, 내부에 nested @SpringBootConfiguration static class TestBootstrap 를 선언했다.

  • @WebMvcTest 는 명시적 config 가 없으면 AnnotatedClassFinder(SpringBootConfiguration.class) 로 테스트 클래스 패키지에서 @SpringBootConfiguration 을 찾아 컨텍스트 소스로 삼는다. OperationalContractRuntimeTest 는 원래 CaSkeletonApplication (@SpringBootApplication, scanBasePackages="dev.caskeleton") 을 찾아 그 컴포넌트 스캔으로 EnvelopeBodyAdvice/GlobalExceptionHandler 를 등록했다.
  • 같은 패키지에 두 번째 @SpringBootConfiguration (EnvelopeMetaContractTest.TestBootstrap) 가 생기자 config 탐지가 교란됐다. TestBootstrap@EnableAutoConfiguration 만 있고 scanBasePackages 가 없어, 그 컨텍스트에는 EnvelopeBodyAdvice 가 스캔되지 않는다 → 빈 부재 + 응답 미-wrap.
  • 별개로 EnvelopeMetaContractTest 자신의 BindException 은, 그 슬라이스가 test 프로파일 없이 production application.yml 을 로드해 ${OIDC_ISSUER_URI} 등 미해소 placeholder 가 @Validated settings 의 Assert.state(...) 를 깨뜨린 것.

핵심 교훈: @SpringBootConfiguration(또는 nested 형태)을 다른 Spring Boot 테스트와 같은 패키지에 두면, 그 패키지의 config 자동 탐지를 조용히 오염시킬 수 있다. 컴파일/실행은 되지만 다른 테스트의 컨텍스트가 바뀐다.

해결 / Resolution

EnvelopeMetaContractTest 를 폐기하고, 검증 대상 3개 컴포넌트(EnvelopeBodyAdvice/GlobalExceptionHandler/RequestLoggingFilter)가 모두 adapter-web 소속이라는 점에 착안해 adapter-web 의 standalone MockMvc 통합 테스트(EnvelopeMetaIntegrationTest)로 재작성:

mvc = MockMvcBuilders.standaloneSetup(new Probe())
        .addFilter(new RequestLoggingFilter())
        .setControllerAdvice(new EnvelopeBodyAdvice(), new GlobalExceptionHandler())
        .build();
  • Spring 컨텍스트가 없으므로 production placeholder 바인딩도, @SpringBootConfiguration 오염도, security 필터 체인도 없다.
  • 필터가 실제로 돌아 snake_case MDC 를 채우므로 meta.requestId/meta.traceId 가 채워진 채로 success/5xx 두 경로를 검증.
  • problemdetails.enabled=false 의 env-property 단언은 standalone 에서 불가 → 폐기. ProblemDetail 금지는 이미 ArchUnit no_problem_detail_usage 규칙 + application.yml 핀이 커버.

검증: ./gradlew check (전 모듈 test + verifyCleanArchitectureDependencies + ArchUnit CleanArchitectureTest) BUILD SUCCESSFUL.

회고 / Lessons (재발 방지)

  • Spring Boot 슬라이스 테스트(@WebMvcTest 등)의 nested @SpringBootConfiguration 은 같은 패키지의 다른 테스트와 충돌할 수 있다. 슬라이스가 자기만의 config 가 필요하면 (a) 전용 패키지로 분리하거나 (b) @ContextConfiguration 으로 명시 지정하거나 (c) 애초에 컨텍스트 없는 standalone MockMvc 를 쓴다.
  • adapter 컴포넌트만으로 검증 가능한 계약은 app-bootstrap 풀 컨텍스트가 아니라 해당 adapter 모듈의 standalone 테스트가 더 견고하고 빠르다 (placeholder/security 부담 없음).
  • 미커밋 상태에서 회귀 원인 격리는 git stash -u / git stash push -- <files> / 파일 mv비파괴 실험이 가장 확실 — 정적 추론보다 한 번의 격리 실행이 빠르다.

관련