2025-04-08T19:56:24
This commit is contained in:
166
docs/webflux/08_웹플럭스에서의 테스트.md
Normal file
166
docs/webflux/08_웹플럭스에서의 테스트.md
Normal file
@@ -0,0 +1,166 @@
|
||||
아래는 **"스프링부트 웹플럭스 시리즈"**의 **8장: 웹플럭스에서의 테스트"**에 대한 초안입니다. 7장에서 만든 REST API를 기반으로 `WebTestClient`를 활용한 테스트를 다루며, 단위 테스트와 통합 테스트 예제를 포함했습니다. 코드도 간략히 유지하며 초보자가 따라 하기 쉽게 자연스러운 문체로 작성했습니다.
|
||||
|
||||
---
|
||||
|
||||
## 8. 웹플럭스에서의 테스트
|
||||
|
||||
7장에서 REST API를 완성했으니, 이제 제대로 작동하는지 확인해볼 차례입니다. 웹플럭스는 비동기 특성 때문에 테스트 방식이 기존 스프링 MVC와 조금 다릅니다. 이번 장에서는 `WebTestClient`를 사용해 웹플럭스 애플리케이션을 테스트하는 방법을 배워보겠습니다. 단위 테스트와 통합 테스트를 실습하며, 모킹과 비동기 테스트 팁도 함께 다룰게요. 준비되셨죠?
|
||||
|
||||
### WebTestClient를 활용한 테스트 작성
|
||||
|
||||
`WebTestClient`는 웹플럭스 애플리케이션의 HTTP 엔드포인트를 테스트하기 위한 도구입니다. 실제 서버를 띄우거나 모킹된 환경에서 테스트할 수 있어 유연합니다. 먼저, 테스트 의존성이 `build.gradle`에 있는지 확인하세요 (3장에서 추가했어야 함):
|
||||
|
||||
```gradle
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
testImplementation 'io.projectreactor:reactor-test'
|
||||
```
|
||||
|
||||
### 단위 테스트 예제
|
||||
|
||||
먼저, `UserController`의 로직을 단위 테스트로 검증해보겠습니다. 데이터베이스 호출을 모킹하여 컨트롤러만 테스트합니다. `src/test/java/com/example/demo`에 `UserControllerTest` 클래스를 만듭니다:
|
||||
|
||||
```java
|
||||
package com.example.demo;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@WebFluxTest(UserController.class)
|
||||
public class UserControllerTest {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webTestClient;
|
||||
|
||||
@MockBean
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Test
|
||||
public void testGetUserById() {
|
||||
User user = new User();
|
||||
user.setId(1L);
|
||||
user.setName("Alice");
|
||||
user.setEmail("alice@example.com");
|
||||
|
||||
when(userRepository.findById(1L)).thenReturn(Mono.just(user));
|
||||
|
||||
webTestClient.get()
|
||||
.uri("/users/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(User.class)
|
||||
.isEqualTo(user);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUserNotFound() {
|
||||
when(userRepository.findById(999L)).thenReturn(Mono.empty());
|
||||
|
||||
webTestClient.get()
|
||||
.uri("/users/999")
|
||||
.exchange()
|
||||
.expectStatus().isNotFound()
|
||||
.expectBody(String.class)
|
||||
.isEqualTo("User with ID 999 not found");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `@WebFluxTest`: 컨트롤러만 테스트하도록 웹플럭스 환경을 설정.
|
||||
- `@MockBean`: `UserRepository`를 모킹하여 실제 DB 호출을 대체.
|
||||
- `WebTestClient`: GET 요청을 보내고 응답을 검증.
|
||||
|
||||
`testGetUserById`는 성공 케이스, `testGetUserNotFound`는 404 에러 케이스를 테스트합니다. `./gradlew test`로 실행하면 결과가 콘솔에 표시됩니다.
|
||||
|
||||
### 통합 테스트 예제
|
||||
|
||||
이번엔 실제 데이터베이스와 연동한 통합 테스트를 해보겠습니다. `src/test/java/com/example/demo`에 `UserControllerIntegrationTest`를 추가합니다:
|
||||
|
||||
```java
|
||||
package com.example.demo;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public class UserControllerIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webTestClient;
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Test
|
||||
public void testCreateAndGetUser() {
|
||||
User user = new User();
|
||||
user.setName("Bob");
|
||||
user.setEmail("bob@example.com");
|
||||
|
||||
webTestClient.post()
|
||||
.uri("/users")
|
||||
.body(Mono.just(user), User.class)
|
||||
.exchange()
|
||||
.expectStatus().isCreated()
|
||||
.expectBody(User.class)
|
||||
.value(savedUser -> {
|
||||
webTestClient.get()
|
||||
.uri("/users/" + savedUser.getId())
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(User.class)
|
||||
.isEqualTo(savedUser);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `@SpringBootTest`: 전체 애플리케이션 컨텍스트를 로드하며, 랜덤 포트로 서버를 띄움.
|
||||
- `testCreateAndGetUser`: 사용자를 생성하고, 생성된 ID로 조회까지 테스트.
|
||||
|
||||
이 테스트는 실제 MariaDB와 연동되므로, `application.yaml`의 설정이 올바른지 확인하세요.
|
||||
|
||||
### 모킹과 비동기 테스트 팁
|
||||
|
||||
1. **모킹 활용**: `Mockito`로 리포지토리나 외부 서비스를 모킹하면 의존성을 줄이고 빠르게 테스트할 수 있습니다. 단위 테스트에서 유용하죠.
|
||||
2. **비동기 처리 확인**: `StepVerifier`를 사용하면 `Mono`나 `Flux`의 비동기 흐름을 더 정밀하게 검증할 수 있습니다. 예를 들어:
|
||||
|
||||
```java
|
||||
@Test
|
||||
public void testMonoWithStepVerifier() {
|
||||
User user = new User();
|
||||
user.setId(1L);
|
||||
user.setName("Alice");
|
||||
|
||||
when(userRepository.findById(1L)).thenReturn(Mono.just(user));
|
||||
|
||||
Mono<User> userMono = userRepository.findById(1L);
|
||||
StepVerifier.create(userMono)
|
||||
.expectNext(user)
|
||||
.verifyComplete();
|
||||
}
|
||||
```
|
||||
|
||||
- `StepVerifier`: 리액티브 스트림의 이벤트를 단계별로 검증.
|
||||
|
||||
3. **타임아웃 설정**: 비동기 테스트가 너무 오래 걸리면 실패로 간주하도록 설정하세요. `WebTestClient`에 `.responseTimeout(Duration.ofSeconds(5))`를 추가할 수 있습니다.
|
||||
|
||||
### 테스트 실행해보기 _
|
||||
|
||||
`./gradlew test`를 실행하면 단위 테스트와 통합 테스트가 모두 실행됩니다. 콘솔에서 성공/실패 여부를 확인할 수 있습니다. 통합 테스트는 MariaDB가 실행 중이어야 하니, 서버가 켜져 있는지 체크하세요.
|
||||
|
||||
### 마무리
|
||||
|
||||
`WebTestClient`로 웹플럭스 API를 테스트하며, 단위 테스트와 통합 테스트의 차이도 경험해봤습니다. 비동기 환경에서의 테스트는 처음엔 낯설 수 있지만, 익숙해지면 애플리케이션의 안정성을 크게 높일 수 있습니다. 다음 장에서는 성능 최적화와 디버깅을 다루며, 웹플럭스를 실무에서 더 단단하게 다듬는 법을 배워보겠습니다. 테스트 작성의 재미를 느끼셨길 바랍니다!
|
||||
|
||||
---
|
||||
|
||||
이 장은 `WebTestClient`를 중심으로 실습을 구성했으며, 단위/통합 테스트와 비동기 팁을 간결히 다뤘습니다. 추가 예제나 수정이 필요하면 말씀해주세요!
|
||||
Reference in New Issue
Block a user