728x90
WebTestClient 기반 공통 로깅과 JSON 출력까지 자동화
✅ 목표
WebFlux는 비동기 기반이기 때문에 기존 MockMvc 방식과는 다르게 WebTestClient를 사용합니다.
테스트가 복잡해지기 전에, 저는 다음을 목표로 구조를 설계했습니다:
- 모든 테스트에서 요청/응답 로그 자동 수집
- 응답 시간, 헤더, JSON 바디를 보기 좋게 출력
- 중복 없이 재사용 가능한 공통 베이스 테스트 클래스 구성
🛠️ 프로젝트 환경 요약
// build.gradle.kts
dependencies {
implementation("org.springframework.boot:spring-boot-starter-webflux") // WebFlux
implementation("org.springframework.boot:spring-boot-starter-data-r2dbc") // R2DBC
implementation("org.springdoc:springdoc-openapi-starter-webflux-ui:2.5.0") // Swagger
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1") // JSON 변환
testImplementation("org.springframework.boot:spring-boot-starter-test") // WebTestClient 포함
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin") // Jackson + Kotlin
}
🧩 WebTestClient로 테스트할 때 불편했던 점
- 요청 URL, 헤더 확인하려면 매번 println 써야 했음
- 응답 시간도 수동으로 재야 했고
- JSON 응답 바디는 한 줄로 출력돼서 보기 불편함
- 테스트가 많아지면 로그 관리가 힘들어짐
그래서 아예 공통 베이스 테스트 클래스를 만들어서 자동화했습니다.
🧱 DebugWebFluxTestSupport.kt
@TestPropertySource(properties = ["jasypt.encryptor.password=temp"]) // 테스트용 암호화 우회 설정
abstract class DebugWebFluxTestSupport {
protected open lateinit var webTestClient: WebTestClient // 테스트 클라이언트
protected val mapper = ObjectMapper().registerKotlinModule() // JSON 변환용
private lateinit var startTime: Instant // 요청 시작 시간
protected lateinit var lastRequestInfo: String // 요청 로그
protected lateinit var lastResponseInfo: String // 응답 로그
protected var lastResponseBodyJson: String? = null // 응답 JSON
@BeforeEach
fun setupLoggingFilter() {
webTestClient = webTestClient.mutate()
.responseTimeout(Duration.ofSeconds(30)) // 응답 타임아웃
.filter { request, next ->
lastRequestInfo = buildString {
appendLine("🔸[REQUEST] ${request.method()} ${request.url()}")
request.headers().forEach { (k, v) ->
appendLine(" ➜ $k: ${v.joinToString()}") // 요청 헤더 출력
}
}
startTime = Instant.now() // 시간 기록 시작
next.exchange(request).doOnNext { response ->
val duration = Duration.between(startTime, Instant.now()).toMillis()
lastResponseInfo = buildString {
appendLine("🔹[RESPONSE] Status: ${response.statusCode()}, Time: ${duration}ms")
response.headers().asHttpHeaders().forEach { (k, v) ->
appendLine(" ⇦ $k: ${v.joinToString()}") // 응답 헤더 출력
}
}
}
}.build()
}
@AfterEach
fun printDebugSummary() {
println("\n✅ 테스트 종료 후 전체 요청/응답 정보 요약:")
println(lastRequestInfo)
println(lastResponseInfo)
println("📦 응답 바디 (JSON Pretty):\n${lastResponseBodyJson ?: "(바디 없음)"}")
}
protected fun setResponseBody(body: Any?) {
lastResponseBodyJson = mapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(body) // JSON 예쁘게 저장
}
}
✅ 실제 테스트 예제
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ContentControllerTest : DebugWebFluxTestSupport() {
@Autowired
override lateinit var webTestClient: WebTestClient // 부모 클래스 필드 초기화
@Test
fun getContentsTest() {
val result = webTestClient
.get().uri("/v1/content") // API 호출
.accept(MediaType.APPLICATION_JSON) // JSON 응답 기대
.exchange()
.expectStatus().isOk // 200 OK 확인
.returnResult(CommonRes::class.java) // 응답 본문 타입 지정
val bodyList = result.responseBody.collectList().block() // Flux → List로 변환
setResponseBody(bodyList) // JSON 로그 저장
}
}
✅ 실행 결과 예시 (자동 출력)
✅ 테스트 종료 후 전체 요청/응답 정보 요약:
🔸[REQUEST] GET http://localhost:62581/v1/content
➜ Accept: application/json
🔹[RESPONSE] Status: 200 OK, Time: 45ms
⇦ Content-Type: application/json
📦 응답 바디 (JSON Pretty):
[
{
"status": "SUCCESS",
"data": [...]
}
]
✨ 효과 정리
- 매 테스트마다 요청/응답 로그를 신경 쓸 필요 없음
- JSON도 보기 좋게 자동 출력됨
- 공통 베이스로 테스트 작성이 훨씬 깔끔해짐
- 테스트 실패 시 디버깅이 쉬워짐
📌 다음 할 일
- POST, PUT 요청도 위 방식으로 확장
- .expectBody().jsonPath() 등으로 본문 필드 검증 추가
- @WebFluxTest 단위 테스트 구성도 따로 분리 예정
이 구조는 WebFlux 프로젝트에서 안정적인 테스트 흐름을 만들고 싶은 분들께 추천드립니다.
위 코드 전체는 테스트 코드이기 때문에 실제 API 작성 전에 먼저 테스트부터 잡아보셔도 좋아요.
참고
https://github.com/Raconer/SpringWebFlux
GitHub - Raconer/SpringWebFlux: 스프링 웹 플럭스 공부용
스프링 웹 플럭스 공부용. Contribute to Raconer/SpringWebFlux development by creating an account on GitHub.
github.com
728x90
'BackEnd > Spring WebFlux' 카테고리의 다른 글
Spring WebFlux에서 @RestController vs @Component (0) | 2025.06.17 |
---|---|
Spring WebFlux에서 MySQL을 사용하는 방법: JDBC vs R2DBC 완전 정리 (0) | 2025.04.28 |
부록 - Spring Boot MVC vs Spring WebFlux 비교 (0) | 2025.04.28 |
Spring WebFlux 심화 시리즈 (4) - WebClient를 활용한 대용량 데이터 처리 (0) | 2025.04.28 |
Spring WebFlux 심화 시리즈 (3) - Flux로 페이징 처리하는 방법 (0) | 2025.04.28 |