package kr.itn.itnhub; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.testcontainers.containers.PostgreSQLContainer; /** * Spring 컨텍스트를 실제로 로딩해야 하는 테스트의 공통 베이스. * *

Testcontainers PostgreSQL 컨테이너를 띄우고, 애플리케이션 구동에 필요한 * {@code mattermost.*} / {@code app.admin.*} 필수 프로퍼티를 더미 값으로 채워 * 컨텍스트 리프레시가 실제 외부 시스템 없이도 성공하도록 한다.

* *

싱글턴 컨테이너 패턴. 컨테이너는 테스트 JVM 전체에서 단 한 번만 * static 초기화 블록에서 직접 {@code start()} 하고, JUnit5의 {@code @Testcontainers}/ * {@code @Container} 확장은 의도적으로 쓰지 않는다. 그 확장을 쓰면 매 테스트 클래스가 * 끝날 때마다 컨테이너가 stop 되는데, Spring의 테스트 컨텍스트 캐시는 동일한 설정을 * 공유하는 다음 {@code AbstractDbTest} 하위 클래스에 대해 이미 만들어진 * {@code ApplicationContext}(및 그 안에서 죽은 포트를 가리키는 {@code DataSource})를 * 그대로 재사용해버려, 두 번째로 실행되는 DB 테스트 클래스가 "Connection refused"로 * 깨진다(전체 스위트에서만 재현되고 단독 실행 시엔 통과하는 이유). 여기서 컨테이너를 * 한 번만 띄우고 절대 {@code stop()} 하지 않으면(JVM 종료 시 Ryuk 사이드카가 정리) * 모든 하위 테스트 클래스가 동일한 살아있는 컨테이너를 공유하므로 이 문제가 사라진다.

*/ @SpringBootTest public abstract class AbstractDbTest { @ServiceConnection static final PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:16"); static { postgres.start(); } @DynamicPropertySource static void secrets(DynamicPropertyRegistry registry) { registry.add("app.admin.username", () -> "admin"); registry.add("app.admin.password", () -> "test-password"); registry.add("mattermost.base-url", () -> "http://localhost:1"); registry.add("mattermost.token", () -> "test-token"); registry.add("mattermost.team-id", () -> "test-team"); } }