Loading...
Spring Framework Reference Documentation 7.0.2의 WebTestClient의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
WebTestClient은 서버 애플리케이션을 테스트하기 위해 설계된 HTTP 클라이언트입니다. 이는 Spring의 WebClient를 감싸고 이를 사용해 요청을 수행하지만, 응답을 검증하기 위한 테스트용 파사드를 노출합니다. WebTestClient는 end-to-end HTTP 테스트를 수행하는 데 사용할 수 있습니다.
또한 mock 서버 요청과 응답 객체를 통해, 실행 중인 서버 없이 Spring MVC와 Spring WebFlux 애플리케이션을 테스트하는 데도 사용할 수 있습니다.
WebTestClient를 설정하려면 바인딩할 서버 설정을 선택해야 합니다. 이는 여러 mock 서버 설정 선택지 중 하나일 수도 있고, live 서버에 대한 커넥션일 수도 있습니다.
이 설정은 실행 중인 서버 없이, mock 요청과 응답 객체를 통해 특정 컨트롤러들을 테스트할 수 있게 해줍니다.
WebFlux 애플리케이션의 경우, 다음과 같이 WebFlux Java config에 상응하는 인프라스트럭처를 로드하고, 주어진 컨트롤러들을 등록하며, 요청을 처리하기 위한 WebHandler chain을 생성합니다:
1WebTestClient client = 2 WebTestClient.bindToController(new TestController()).build();
1val client = WebTestClient.bindToController(TestController()).build()
Spring MVC의 경우, StandaloneMockMvcBuilder에 위임하여 WebMvc Java config에 상응하는 인프라스트럭처를 로드하고, 주어진 컨트롤러들을 등록하며, 요청을 처리하기 위한 MockMvc 인스턴스를 생성하는 다음 설정을 사용합니다:
1WebTestClient client = 2 MockMvcWebTestClient.bindToController(new TestController()).build();
1val client = MockMvcWebTestClient.bindToController(TestController()).build()
ApplicationContext이 설정은 Spring MVC 또는 Spring WebFlux 인프라스트럭처와 컨트롤러 선언이 포함된 Spring 설정을 로드하고, 실행 중인 서버 없이 mock 요청과 응답 객체를 통해 요청을 처리하는 데 사용할 수 있게 해줍니다.
WebFlux의 경우, Spring ApplicationContext를 WebHttpHandlerBuilder에 전달하여 요청을 처리할 WebHandler chain을 생성하는 다음 설정을 사용합니다:
1@SpringJUnitConfig(WebConfig.class) (1) 2class MyTests { 3 4 WebTestClient client; 5 6 @BeforeEach 7 void setUp(ApplicationContext context) { (2) 8 client = WebTestClient.bindToApplicationContext(context).build(); (3) 9 } 10}
| 1 | 로드할 설정을 지정합니다 |
| 2 | 설정을 주입합니다 |
| 3 | WebTestClient를 생성합니다 |
1@SpringJUnitConfig(WebConfig::class) (1) 2class MyTests { 3 4 lateinit var client: WebTestClient 5 6 @BeforeEach 7 fun setUp(context: ApplicationContext) { (2) 8 client = WebTestClient.bindToApplicationContext(context).build() (3) 9 } 10}
| 1 | 로드할 설정을 지정합니다 |
| 2 | 설정을 주입합니다 |
| 3 | WebTestClient를 생성합니다 |
Spring MVC의 경우, Spring ApplicationContext를 MockMvcBuilders.webAppContextSetup에 전달하여 요청을 처리할 MockMvc 인스턴스를 생성하는 다음 설정을 사용합니다:
1@ExtendWith(SpringExtension.class) 2@WebAppConfiguration("classpath:META-INF/web-resources") (1) 3@ContextHierarchy({ 4 @ContextConfiguration(classes = RootConfig.class), 5 @ContextConfiguration(classes = WebConfig.class) 6}) 7class MyTests { 8 9 @Autowired 10 WebApplicationContext wac; (2) 11 12 WebTestClient client; 13 14 @BeforeEach 15 void setUp() { 16 client = MockMvcWebTestClient.bindToApplicationContext(this.wac).build(); (3) 17 } 18}
| 1 | 로드할 설정을 지정합니다 |
| 2 | 설정을 주입합니다 |
| 3 | WebTestClient를 생성합니다 |
1@ExtendWith(SpringExtension.class) 2@WebAppConfiguration("classpath:META-INF/web-resources") (1) 3@ContextHierarchy({ 4 @ContextConfiguration(classes = RootConfig.class), 5 @ContextConfiguration(classes = WebConfig.class) 6}) 7class MyTests { 8 9 @Autowired 10 lateinit var wac: WebApplicationContext; (2) 11 12 lateinit var client: WebTestClient 13 14 @BeforeEach 15 fun setUp() { (2) 16 client = MockMvcWebTestClient.bindToApplicationContext(wac).build() (3) 17 } 18}
| 1 | 로드할 설정을 지정합니다 |
| 2 | 설정을 주입합니다 |
| 3 | WebTestClient를 생성합니다 |
이 설정은 실행 중인 서버 없이, mock 요청과 응답 객체를 통해 functional endpoint를 테스트할 수 있게 해줍니다.
WebFlux의 경우, RouterFunctions.toWebHandler에 위임하여 요청을 처리할 서버 설정을 생성하는 다음 설정을 사용합니다:
1RouterFunction<?> route = ... 2client = WebTestClient.bindToRouterFunction(route).build();
1val route: RouterFunction<*> = ... 2val client = WebTestClient.bindToRouterFunction(route).build()
Spring MVC의 경우 현재 WebMvc functional endpoint를 테스트할 수 있는 옵션이 없습니다.
이 설정은 실행 중인 서버에 연결하여 전체 end-to-end HTTP 테스트를 수행합니다:
1client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();
1client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build()
앞에서 설명한 서버 설정 옵션에 더해, 기본 URL, 기본 헤더, 클라이언트 필터 등 클라이언트 옵션도 설정할 수 있습니다. 이러한 옵션은 bindToServer() 이후에 바로 사용할 수 있습니다.
그 외의 모든 설정 옵션에 대해서는, 다음과 같이 configureClient()를 사용하여 서버 설정에서 클라이언트 설정으로 전환해야 합니다:
1client = WebTestClient.bindToController(new TestController()) 2 .configureClient() 3 .baseUrl("/test") 4 .apiVersionInserter(ApiVersionInserter.fromHeader("API-Version").build()) 5 .build();
1client = WebTestClient.bindToController(TestController()) 2 .configureClient() 3 .baseUrl("/test") 4 .apiVersionInserter(ApiVersionInserter.fromHeader("API-Version").build()) 5 .build()
WebClient와 WebTestClient는 exchange() 호출 시점까지 동일한 API를 가집니다. 그 이후에는, WebTestClient가 응답을 검증하기 위한 두 가지 대안을 제공합니다:
assertThat() 구문을 통해 응답을 검증하는 AssertJ Integrationform data, multipart data 등을 포함한 임의의 콘텐츠로 요청을 준비하는 예시는<br>WebClient 문서를 참고하세요.
응답 상태와 헤더를 assert하려면 다음을 사용합니다:
1client.get().uri("/persons/1") 2 .accept(MediaType.APPLICATION_JSON) 3 .exchange() 4 .expectStatus().isOk() 5 .expectHeader().contentType(MediaType.APPLICATION_JSON);
1client.get().uri("/persons/1") 2 .accept(MediaType.APPLICATION_JSON) 3 .exchange() 4 .expectStatus().isOk() 5 .expectHeader().contentType(MediaType.APPLICATION_JSON)
모든 expectation이, 그중 하나가 실패하더라도 assert되기를 원한다면, 여러 개의 expect*(..) 호출을 체인으로 연결하는 대신 expectAll(..)을 사용할 수 있습니다. 이 기능은 AssertJ의 soft assertions 지원 및 JUnit Jupiter의 assertAll() 지원과 유사합니다.
1client.get().uri("/persons/1") 2 .accept(MediaType.APPLICATION_JSON) 3 .exchange() 4 .expectAll( 5 spec -> spec.expectStatus().isOk(), 6 spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON) 7 );
1client.get().uri("/persons/1") 2 .accept(MediaType.APPLICATION_JSON) 3 .exchange() 4 .expectAll( 5 { spec -> spec.expectStatus().isOk() }, 6 { spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON) } 7 )
그런 다음 다음 중 하나를 통해 응답 body를 디코딩하는 것을 선택할 수 있습니다:
expectBody(Class<T>): 단일 객체로 디코딩합니다.expectBodyList(Class<T>): 객체를 디코딩하고 List<T>로 수집합니다.expectBody(): JSON Content를 위해 byte[]로 디코딩하거나, body가 비어 있는 경우 empty body로 디코딩합니다.그리고 결과로 나온 상위 수준 객체에 대해 assertion을 수행합니다:
1client.get().uri("/persons") 2 .exchange() 3 .expectStatus().isOk() 4 .expectBodyList(Person.class).hasSize(3).contains(person);
1import org.springframework.test.web.reactive.server.expectBodyList 2 3client.get().uri("/persons") 4 .exchange() 5 .expectStatus().isOk() 6 .expectBodyList<Person>().hasSize(3).contains(person)
built-in assertion이 충분하지 않은 경우, 대신 객체를 consume하고 다른 assertion을 수행할 수 있습니다:
1import org.springframework.test.web.reactive.server.expectBody 2 3client.get().uri("/persons/1") 4 .exchange() 5 .expectStatus().isOk() 6 .expectBody(Person.class) 7 .consumeWith(result -> { 8 // custom assertions (for example, AssertJ)... 9 });
1client.get().uri("/persons/1") 2 .exchange() 3 .expectStatus().isOk() 4 .expectBody<Person>() 5 .consumeWith { 6 // custom assertions (for example, AssertJ)... 7 }
또는 워크플로를 종료하고 EntityExchangeResult를 얻을 수도 있습니다:
1EntityExchangeResult<Person> result = client.get().uri("/persons/1") 2 .exchange() 3 .expectStatus().isOk() 4 .expectBody(Person.class) 5 .returnResult();
1import org.springframework.test.web.reactive.server.expectBody 2 3val result = client.get().uri("/persons/1") 4 .exchange() 5 .expectStatus().isOk 6 .expectBody<Person>() 7 .returnResult()
generics가 있는 target type으로 디코드해야 할 경우,<br>
Class<T>대신<br>ParameterizedTypeReference를 받는<br>overloaded 메서드를 사용하세요.
응답에 콘텐츠가 없을 것으로 예상되는 경우, 다음과 같이 이를 assert할 수 있습니다:
1client.post().uri("/persons") 2 .body(personMono, Person.class) 3 .exchange() 4 .expectStatus().isCreated() 5 .expectBody().isEmpty();
1client.post().uri("/persons") 2 .bodyValue(person) 3 .exchange() 4 .expectStatus().isCreated() 5 .expectBody().isEmpty()
응답 콘텐츠를 무시하려는 경우, 다음은 어떠한 assertion 없이 콘텐츠를 해제합니다:
1client.get().uri("/persons/123") 2 .exchange() 3 .expectStatus().isNotFound() 4 .expectBody(Void.class);
1client.get().uri("/persons/123") 2 .exchange() 3 .expectStatus().isNotFound 4 .expectBody<Unit>()
target type 없이 expectBody()를 사용하여 상위 수준 객체를 통하는 대신 raw 콘텐츠에 대해 assertion을 수행할 수 있습니다.
JSONAssert를 사용해 전체 JSON 콘텐츠를 검증하려면 다음과 같이 합니다:
1client.get().uri("/persons/1") 2 .exchange() 3 .expectStatus().isOk() 4 .expectBody() 5 .json("{"name":"Jane"}")
1client.get().uri("/persons/1") 2 .exchange() 3 .expectStatus().isOk() 4 .expectBody() 5 .json("{"name":"Jane"}")
JSONPath를 사용해 JSON 콘텐츠를 검증하려면 다음과 같이 합니다:
1client.get().uri("/persons") 2 .exchange() 3 .expectStatus().isOk() 4 .expectBody() 5 .jsonPath("$[0].name").isEqualTo("Jane") 6 .jsonPath("$[1].name").isEqualTo("Jason");
1client.get().uri("/persons") 2 .exchange() 3 .expectStatus().isOk() 4 .expectBody() 5 .jsonPath("$[0].name").isEqualTo("Jane") 6 .jsonPath("$[1].name").isEqualTo("Jason")
"text/event-stream"이나 "application/x-ndjson"과 같은 잠재적으로 무한한 스트림을 테스트하려면, 먼저 응답 상태와 헤더를 검증한 다음 FluxExchangeResult를 얻습니다:
1FluxExchangeResult<MyEvent> result = client.get().uri("/events") 2 .accept(TEXT_EVENT_STREAM) 3 .exchange() 4 .expectStatus().isOk() 5 .returnResult(MyEvent.class);
1import org.springframework.test.web.reactive.server.returnResult 2 3val result = client.get().uri("/events") 4 .accept(TEXT_EVENT_STREAM) 5 .exchange() 6 .expectStatus().isOk() 7 .returnResult<MyEvent>()
이제 reactor-test의 StepVerifier를 사용해 응답 스트림을 consume할 준비가 되었습니다:
1Flux<Event> eventFlux = result.getResponseBody(); 2 3StepVerifier.create(eventFlux) 4 .expectNext(person) 5 .expectNextCount(4) 6 .consumeNextWith(p -> ...) 7 .thenCancel() 8 .verify();
1val eventFlux = result.getResponseBody() 2 3StepVerifier.create(eventFlux) 4 .expectNext(person) 5 .expectNextCount(4) 6 .consumeNextWith { p -> ... } 7 .thenCancel() 8 .verify()
WebTestClientResponse는 AssertJ integration을 위한 main entry point입니다. 이는 exchange의 ResponseSpec을 감싸 assertThat() 구문 사용을 가능하게 하는 AssertProvider입니다.
예를 들면 다음과 같습니다:
1ResponseSpec spec = client.get().uri("/persons").exchange(); 2 3WebTestClientResponse response = WebTestClientResponse.from(spec); 4assertThat(response).hasStatusOk(); 5assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN); 6// ...
1val spec = client.get().uri("/persons").exchange() 2 3val response = WebTestClientResponse.from(spec) 4assertThat(response).hasStatusOk() 5assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN) 6// ...
또한 먼저 built-in 워크플로를 사용한 다음, AssertJ를 계속 사용하기 위해 wrap할 ExchangeResult를 얻을 수도 있습니다. 예를 들면 다음과 같습니다:
1ExchangeResult result = client.get().uri("/persons").exchange() 2 . // ... 3 .returnResult(); 4 5WebTestClientResponse response = WebTestClientResponse.from(result); 6assertThat(response).hasStatusOk(); 7assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN); 8// ...
1val result = client.get().uri("/persons").exchange() 2 . // ... 3 .returnResult() 4 5val response = WebTestClientResponse.from(spec) 6assertThat(response).hasStatusOk() 7assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN) 8// ...
WebTestClient는 HTTP 클라이언트이며, 따라서 상태, 헤더, body를 포함해 클라이언트 응답에 있는 것만 검증할 수 있습니다.
MockMvc 서버 설정으로 Spring MVC 애플리케이션을 테스트할 때는, 서버 응답에 대해 추가 assertion을 수행할 수 있는 추가 선택지가 있습니다. 이를 위해서는, body를 assert한 후 ExchangeResult를 얻는 것부터 시작합니다:
1// For a response with a body 2EntityExchangeResult<Person> result = client.get().uri("/persons/1") 3 .exchange() 4 .expectStatus().isOk() 5 .expectBody(Person.class) 6 .returnResult(); 7 8// For a response without a body 9EntityExchangeResult<Void> result = client.get().uri("/path") 10 .exchange() 11 .expectBody().isEmpty();
1// For a response with a body 2val result = client.get().uri("/persons/1") 3 .exchange() 4 .expectStatus().isOk() 5 .expectBody<Person>() 6 .returnResult() 7 8// For a response without a body 9val result = client.get().uri("/path") 10 .exchange() 11 .expectBody().isEmpty()
그런 다음 MockMvc 서버 응답 assertion으로 전환합니다:
1MockMvcWebTestClient.resultActionsFor(result) 2 .andExpect(model().attribute("integer", 3)) 3 .andExpect(model().attribute("string", "a string value"));
1MockMvcWebTestClient.resultActionsFor(result) 2 .andExpect(model().attribute("integer", 3)) 3 .andExpect(model().attribute("string", "a string value"));
Ahead of Time Support for Tests
RestTestClient