개요
RestTestClient는 Spring Framework 7에서 새롭게 도입된 통합 테스트 도구로, Spring REST API를 손쉽게 테스트할 수 있게 해 준다. 기존에 MockMvc와 WebTestClient의 장점을 하나로 합쳐 단순하고 직관적인 API를 제공하여 쉽게 테스트를 작성할 수 있게 해 준다.
이번 게시글에서는 RestTestClient의 각 상황에 맞는 사용법과 왜 사용해야 하는지에 대하여 알아보자. 자세한 사항은 아래 문서를 참고한다.
공식 문서 : https://docs.spring.io/spring-framework/reference/testing/resttestclient.html
RestTestClient를 사용해야 하는 이유는 무엇일까?
- 모든 유형의 테스트를 위한 일관된 API를 제공한다.
- 유창하고 읽기 쉬운 구문을 제공한다.
- 단위 테스트, 통합 테스트 및 end-to-end 테스트 모두 작성이 가능하다.
- 배우고 사용하기 쉽다.
아래 각 상황에 맞는 예제에 대하여 하나씩 알아보도록 하자.
Gettng Started
사전 준비
Spring Boot 4.1로 프로젝트를 생성하고 Web관련 Starter를 추가한다.
plugins {
id 'java'
id 'org.springframework.boot' version '4.0.1'
id 'io.spring.dependency-management' version '1.1.7'
}
{
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
테스트로 사용할 API Controller를 작성한다.
@RestController
@RequestMapping("/users")
class UserController {
private final UserService userService;
UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
List<User> findAll() {
return this.userService.findAll();
}
@GetMapping("/{id}")
User findById(@PathVariable("id") int id) {
return this.userService.findBydId(id);
}
}
테스트 작성
Bind To Controller
Controller의 로직만 빠르게 테스트할 때 사용한다. (기존 Mockito를 사용한 테스트와 비슷하다.)
class SimpleControllerTests {
private RestTestClient client;
private UserService userService;
@BeforeEach
void setup() {
this.userService = mock(UserService.class);
this.client = RestTestClient.bindToController(new UserController(this.userService)).build();
}
@Test
void findById() {
var id = 1;
// Given
given(this.userService.findBydId(id)).willReturn(new User(id));
// When & Then
this.client.get()
.uri("/users/1")
.exchange()
.expectStatus()
.isOk()
.expectHeader()
.contentType(MediaType.APPLICATION_JSON)
.expectBody()
.json("{\"id\":1}")
.returnResult();
}
}
Bind to MockMvc
Spring MVC 기능(유효성 검사, 보안, 예외 처리 등)을 사용하여 테스트를 작성한다.
@WebMvcTests(UserController.class)
class MockMvcControllerTests {
@Autowired
private MockMvc mockMvc;
@MockitoBean
private UserService userService;
private RestTestClient client;
@BeforeEach
void setup() {
this.client = RestTestClient.bindTo(this.mockMvc).build();
}
@Test
void findAll() {
// Given
given(this.userService.findAll()).willReturn(List.of(new User(0), new User(1)));
// When
var users = this.client.get()
.uri("/users")
.exchange()
.expectStatus()
.isOk()
.expectBody(new ParameterizedTypeReference<List<User>>() {})
.returnResult()
.getResponseBody();
// Then
assertThat(users).hasSize(2);
assertThat(users.getFirst().id()).isEqualTo(2);
}
@Test
void invalideInput() {
var users = this.client.get()
.uri("/users")
.exchange()
.expectStatus()
.isBadRequest();
}
}
Bind To ApplicationContext
실제 서비스와 데이터베이스를 사용하여 애플리케이션 전체를 테스트한다.
HTTP 연결을 사용하지 않는다.
@SpringBootTest
class ApplicationContextControllerTests {
@Autowired
private WebApplicationContext context;
private RestTestClient client;
@BeforeEach
void setup() {
this.client = RestTestClient.bindToApplicationContext(this.context).build();
}
@Test
void findAll() {
var users = this.client.get()
.uri("/users")
.exchange()
.expectStatus()
.isOk()
.expectBody(new ParameterizedTypeReference<List<User>>() {})
.returnResult()
.getResponseBody();
assertThat(users).hasSize(2);
assertThat(users.getFirst().id()).isEqualTo(2);
}
}
Bind To Server
실제 HTTP 요청을 포함한 모든 기능을 테스트한다.
CORS, 헤더 또는 압축과 같은 HTTP 관련 기능을 포함하여 테스트를 작성할 수 있다.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ServerControllerTests {
@LocalServerPort
private int serverPort;
private RestTestClient client;
@BeforeEach
void setup() {
this.client = RestTestClient.bindToServer().baseUrl("http://localhost:" + this.serverPort).build();
}
@Test
void findAll() {
var users = this.client.get()
.uri("/users")
.exchange()
.expectStatus()
.isOk()
.expectBody(new ParameterizedTypeReference<List<User>>() {})
.returnResult()
.getResponseBody();
assertThat(users).hasSize(2);
assertThat(users.getFirst().id()).isEqualTo(0);
}
}
마무리
각 테스트 방법은 각각의 장단점이 존재한다. 그리고 진행하는 프로젝트와 CI/CD 구성에 따라 상황에 맞는 테스트 코드를 작성할 필요가 있을 것이다. 비즈니스 로직이 들어가는 코드에는 대부분 테스트를 작성하지만 Controller는 로직이 많이 넣지 않기 때문에 테스트를 많이 작성하지는 않았을 수 있다. RestTestClient를 사용하여 Controller에도 상황에 맞는 테스트 코드를 작성해 보자.
'dev > spring' 카테고리의 다른 글
| Spring Boot 4 모듈화 (0) | 2025.12.11 |
|---|---|
| Spring - Path Prefix (0) | 2025.08.18 |
| Spring Framework 7.0.0 API Versioning (0) | 2025.07.31 |
| Spring Framework 7.0 - Resilience Features (회복력 기능) (2) | 2025.07.27 |
| Jackson 3.0.0 알아보기 (0) | 2025.07.15 |