아래는 **"스프링부트 웹플럭스 시리즈"**의 **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`를 중심으로 실습을 구성했으며, 단위/통합 테스트와 비동기 팁을 간결히 다뤘습니다. 추가 예제나 수정이 필요하면 말씀해주세요!