diff --git a/framework-docs/modules/ROOT/pages/testing/resttestclient.adoc b/framework-docs/modules/ROOT/pages/testing/resttestclient.adoc index 5fd807111b2..73244a574d5 100644 --- a/framework-docs/modules/ROOT/pages/testing/resttestclient.adoc +++ b/framework-docs/modules/ROOT/pages/testing/resttestclient.adoc @@ -10,7 +10,7 @@ applications without a running server via MockMvc. -[[resttestclient-setup]] +[[resttestclient.setup]] == Setup To set up a `RestTestClient` you need to choose a server setup to bind to. This can be one @@ -18,7 +18,7 @@ of several MockMvc setup choices, or a connection to a live server. -[[resttestclient-controller-config]] +[[resttestclient.controller-config]] === Bind to Controller This setup allows you to test specific controller(s) via mock request and response objects, @@ -42,55 +42,17 @@ Kotlin:: ---- ====== -[[resttestclient-context-config]] +[[resttestclient.context-config]] === Bind to `ApplicationContext` This setup allows you to load Spring configuration with Spring MVC infrastructure and controller declarations and use it to handle requests via mock request and response objects, without a running server. -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes"] ----- - @SpringJUnitConfig(WebConfig.class) // <1> - class MyTests { +include-code::./RestClientContextTests[indent=0] - RestTestClient client; - @BeforeEach - void setUp(ApplicationContext context) { // <2> - client = RestTestClient.bindToApplicationContext(context).build(); // <3> - } - } ----- -<1> Specify the configuration to load -<2> Inject the configuration -<3> Create the `RestTestClient` - -Kotlin:: -+ -[source,kotlin,indent=0,subs="verbatim,quotes"] ----- - @SpringJUnitConfig(WebConfig::class) // <1> - class MyTests { - - lateinit var client: RestTestClient - - @BeforeEach - fun setUp(context: ApplicationContext) { // <2> - client = RestTestClient.bindToApplicationContext(context).build() // <3> - } - } ----- -<1> Specify the configuration to load -<2> Inject the configuration -<3> Create the `RestTestClient` -====== - -[[resttestclient-fn-config]] +[[resttestclient.fn-config]] === Bind to Router Function This setup allows you to test xref:web/webmvc-functional.adoc[functional endpoints] via @@ -115,7 +77,7 @@ Kotlin:: ---- ====== -[[resttestclient-server-config]] +[[resttestclient.server-config]] === Bind to Server This setup connects to a running server to perform full, end-to-end HTTP tests: @@ -139,7 +101,7 @@ Kotlin:: -[[resttestclient-client-config]] +[[resttestclient.client-config]] === Client Config In addition to the server setup options described earlier, you can also configure client @@ -170,7 +132,7 @@ Kotlin:: -[[resttestclient-tests]] +[[resttestclient.tests]] == Writing Tests xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] and `RestTestClient` have @@ -182,70 +144,22 @@ provides two alternative ways to verify the response: -[[resttestclient-workflow]] +[[resttestclient.workflow]] === Built-in Assertions To use the built-in assertions, remain in the workflow after the call to `exchange()`, and use one of the expectation methods. For example: -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes"] ----- - client.get().uri("/persons/1") - .accept(MediaType.APPLICATION_JSON) - .exchange() - .expectStatus().isOk() - .expectHeader().contentType(MediaType.APPLICATION_JSON); ----- +include-code::./RestClientWorkflowTests[tag=test,indent=0] -Kotlin:: -+ -[source,kotlin,indent=0,subs="verbatim,quotes"] ----- - client.get().uri("/persons/1") - .accept(MediaType.APPLICATION_JSON) - .exchange() - .expectStatus().isOk() - .expectHeader().contentType(MediaType.APPLICATION_JSON) ----- -====== If you would like for all expectations to be asserted even if one of them fails, you can use `expectAll(..)` instead of multiple chained `expect*(..)` calls. This feature is similar to the _soft assertions_ support in AssertJ and the `assertAll()` support in JUnit Jupiter. -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes"] ----- - client.get().uri("/persons/1") - .accept(MediaType.APPLICATION_JSON) - .exchange() - .expectAll( - spec -> spec.expectStatus().isOk(), - spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON) - ); ----- +include-code::./RestClientWorkflowTests[tag=soft-assertions,indent=0] -Kotlin:: -+ -[source,kotlin,indent=0,subs="verbatim,quotes"] ----- - client.get().uri("/persons/1") - .accept(MediaType.APPLICATION_JSON) - .exchange() - .expectAll( - { spec -> spec.expectStatus().isOk() }, - { spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON) } - ) ----- -====== You can then choose to decode the response body through one of the following: @@ -256,124 +170,34 @@ You can then choose to decode the response body through one of the following: If the built-in assertions are insufficient, you can consume the object instead and perform any other assertions: -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes"] ----- - client.get().uri("/persons/1") - .exchange() - .expectStatus().isOk() - .expectBody(Person.class) - .consumeWith(result -> { - // custom assertions (for example, AssertJ)... - }); ----- - -Kotlin:: -+ -[source,kotlin,indent=0,subs="verbatim,quotes"] ----- - client.get().uri("/persons/1") - .exchange() - .expectStatus().isOk() - .expectBody() - .consumeWith { - // custom assertions (for example, AssertJ)... - } ----- -====== +include-code::./RestClientWorkflowTests[tag=consume,indent=0] Or you can exit the workflow and obtain a `EntityExchangeResult`: -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes"] ----- - EntityExchangeResult result = client.get().uri("/persons/1") - .exchange() - .expectStatus().isOk() - .expectBody(Person.class) - .returnResult(); ----- +include-code::./RestClientWorkflowTests[tag=result,indent=0] -Kotlin:: -+ -[source,kotlin,indent=0,subs="verbatim,quotes"] ----- - val result = client.get().uri("/persons/1") - .exchange() - .expectStatus().isOk - .expectBody() - .returnResult() ----- -====== TIP: When you need to decode to a target type with generics, look for the overloaded methods that accept {spring-framework-api}/core/ParameterizedTypeReference.html[`ParameterizedTypeReference`] instead of `Class`. -[[resttestclient-no-content]] +[[resttestclient.no-content]] ==== No Content If the response is not expected to have content, you can assert that as follows: -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes"] ----- - client.post().uri("/persons") - .body(person) - .exchange() - .expectStatus().isCreated() - .expectBody().isEmpty(); ----- - -Kotlin:: -+ -[source,kotlin,indent=0,subs="verbatim,quotes"] ----- - client.post().uri("/persons") - .body(person) - .exchange() - .expectStatus().isCreated() - .expectBody().isEmpty() ----- -====== +include-code::./NoContentTests[tag=emptyBody,indent=0] If you want to ignore the response content, the following releases the content without any assertions: -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes"] ----- - client.get().uri("/persons/123") - .exchange() - .expectStatus().isNotFound() - .expectBody(Void.class); ----- +include-code::./NoContentTests[tag=ignoreBody,indent=0] -Kotlin:: -+ -[source,kotlin,indent=0,subs="verbatim,quotes"] ----- - client.get().uri("/persons/123") - .exchange() - .expectStatus().isNotFound - .expectBody() ----- -====== +NOTE: Consuming the response body (for example, with `expectBody`) is required if your tests are running with +leak detection for pooled buffers. Without that, the tool will report buffers being leaked. -[[resttestclient-json]] +[[resttestclient.json]] ==== JSON Content You can use `expectBody()` without a target type to perform assertions on the raw @@ -381,126 +205,25 @@ content rather than through higher level Object(s). To verify the full JSON content with https://jsonassert.skyscreamer.org[JSONAssert]: -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes"] ----- - client.get().uri("/persons/1") - .exchange() - .expectStatus().isOk() - .expectBody() - .json("{\"name\":\"Jane\"}") ----- +include-code::./JsonTests[tag=jsonBody,indent=0] -Kotlin:: -+ -[source,kotlin,indent=0,subs="verbatim,quotes"] ----- - client.get().uri("/persons/1") - .exchange() - .expectStatus().isOk() - .expectBody() - .json("{\"name\":\"Jane\"}") ----- -====== To verify JSON content with https://github.com/jayway/JsonPath[JSONPath]: -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes"] ----- - client.get().uri("/persons") - .exchange() - .expectStatus().isOk() - .expectBody() - .jsonPath("$[0].name").isEqualTo("Jane") - .jsonPath("$[1].name").isEqualTo("Jason"); ----- - -Kotlin:: -+ -[source,kotlin,indent=0,subs="verbatim,quotes"] ----- - client.get().uri("/persons") - .exchange() - .expectStatus().isOk() - .expectBody() - .jsonPath("$[0].name").isEqualTo("Jane") - .jsonPath("$[1].name").isEqualTo("Jason") ----- -====== +include-code::./JsonTests[tag=jsonPath,indent=0] - -[[resttestclient-assertj]] +[[resttestclient.assertj]] === AssertJ Integration `RestTestClientResponse` is the main entry point for the AssertJ integration. It is an `AssertProvider` that wraps the `ResponseSpec` of an exchange in order to enable use of `assertThat()` statements. For example: -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes"] ----- - ResponseSpec spec = client.get().uri("/persons").exchange(); +include-code::./AssertJTests[tag=withSpec,indent=0] - RestTestClientResponse response = RestTestClientResponse.from(spec); - assertThat(response).hasStatusOk(); - assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN); - // ... ----- - -Kotlin:: -+ -[source,kotlin,indent=0,subs="verbatim,quotes"] ----- - val spec = client.get().uri("/persons").exchange() - - val response = RestTestClientResponse.from(spec) - assertThat(response).hasStatusOk() - assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN) - // ... ----- -====== You can also use the built-in workflow first, and then obtain an `ExchangeResult` to wrap and continue with AssertJ. For example: -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes"] ----- - ExchangeResult result = client.get().uri("/persons").exchange() - . // ... - .returnResult(); - - RestTestClientResponse response = RestTestClientResponse.from(result); - assertThat(response).hasStatusOk(); - assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN); - // ... ----- - -Kotlin:: -+ -[source,kotlin,indent=0,subs="verbatim,quotes"] ----- - val result = client.get().uri("/persons").exchange() - . // ... - .returnResult() - - val response = RestTestClientResponse.from(spec) - assertThat(response).hasStatusOk() - assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN) - // ... ----- -====== +include-code::./AssertJTests[tag=withResult,indent=0] diff --git a/framework-docs/src/main/java/org/springframework/docs/testing/resttestclient/assertj/AssertJTests.java b/framework-docs/src/main/java/org/springframework/docs/testing/resttestclient/assertj/AssertJTests.java new file mode 100644 index 00000000000..84551e342cc --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/testing/resttestclient/assertj/AssertJTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.docs.testing.resttestclient.assertj; + +import org.junit.jupiter.api.Test; + +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.client.ExchangeResult; +import org.springframework.test.web.servlet.client.RestTestClient; +import org.springframework.test.web.servlet.client.assertj.RestTestClientResponse; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AssertJTests { + + RestTestClient client; + + @Test + void withSpec() { + // tag::withSpec[] + RestTestClient.ResponseSpec spec = client.get().uri("/persons").exchange(); + + RestTestClientResponse response = RestTestClientResponse.from(spec); + assertThat(response).hasStatusOk(); + assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN); + // end::withSpec[] + } + + @Test + void withResult() { + // tag::withResult[] + ExchangeResult result = client.get().uri("/persons").exchange().returnResult(); + + RestTestClientResponse response = RestTestClientResponse.from(result); + assertThat(response).hasStatusOk(); + assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN); + // end::withResult[] + } + +} diff --git a/framework-docs/src/main/java/org/springframework/docs/testing/resttestclient/contextconfig/RestClientContextTests.java b/framework-docs/src/main/java/org/springframework/docs/testing/resttestclient/contextconfig/RestClientContextTests.java new file mode 100644 index 00000000000..35a87954625 --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/testing/resttestclient/contextconfig/RestClientContextTests.java @@ -0,0 +1,37 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.docs.testing.resttestclient.contextconfig; + +import org.junit.jupiter.api.BeforeEach; + +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.web.servlet.client.RestTestClient; +import org.springframework.web.context.WebApplicationContext; + + +@SpringJUnitConfig(WebConfig.class) // Specify the configuration to load +public class RestClientContextTests { + + RestTestClient client; + + @BeforeEach + void setUp(WebApplicationContext context) { // Inject the configuration + // Create the `RestTestClient` + client = RestTestClient.bindToApplicationContext(context).build(); + } + +} diff --git a/framework-docs/src/main/java/org/springframework/docs/testing/resttestclient/contextconfig/WebConfig.java b/framework-docs/src/main/java/org/springframework/docs/testing/resttestclient/contextconfig/WebConfig.java new file mode 100644 index 00000000000..5d90979a7cd --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/testing/resttestclient/contextconfig/WebConfig.java @@ -0,0 +1,20 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.docs.testing.resttestclient.contextconfig; + +public class WebConfig { +} diff --git a/framework-docs/src/main/java/org/springframework/docs/testing/resttestclient/json/JsonTests.java b/framework-docs/src/main/java/org/springframework/docs/testing/resttestclient/json/JsonTests.java new file mode 100644 index 00000000000..7aa84615390 --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/testing/resttestclient/json/JsonTests.java @@ -0,0 +1,50 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.docs.testing.resttestclient.json; + +import org.junit.jupiter.api.Test; + +import org.springframework.test.web.servlet.client.RestTestClient; + +public class JsonTests { + + RestTestClient client; + + @Test + void jsonBody() { + // tag::jsonBody[] + client.get().uri("/persons/1") + .exchange() + .expectStatus().isOk() + .expectBody() + .json("{\"name\":\"Jane\"}"); + // end::jsonBody[] + } + + @Test + void jsonPath() { + // tag::jsonPath[] + client.get().uri("/persons") + .exchange() + .expectStatus().isOk() + .expectBody() + .jsonPath("$[0].name").isEqualTo("Jane") + .jsonPath("$[1].name").isEqualTo("Jason"); + // end::jsonPath[] + } + +} diff --git a/framework-docs/src/main/java/org/springframework/docs/testing/resttestclient/nocontent/NoContentTests.java b/framework-docs/src/main/java/org/springframework/docs/testing/resttestclient/nocontent/NoContentTests.java new file mode 100644 index 00000000000..b48ad16d570 --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/testing/resttestclient/nocontent/NoContentTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.docs.testing.resttestclient.nocontent; + +import org.junit.jupiter.api.Test; + +import org.springframework.test.web.servlet.client.RestTestClient; + +public class NoContentTests { + + + RestTestClient client; + + @Test + void emptyBody() { + Person person = new Person("Jane"); + // tag::emptyBody[] + client.post().uri("/persons") + .body(person) + .exchange() + .expectStatus().isCreated() + .expectBody().isEmpty(); + // end::emptyBody[] + } + + @Test + void ignoreBody() { + Person person = new Person("Jane"); + // tag::ignoreBody[] + client.post().uri("/persons") + .body(person) + .exchange() + .expectStatus().isCreated() + .expectBody(Void.class); + // end::ignoreBody[] + } + + record Person(String name) { + + } + +} diff --git a/framework-docs/src/main/java/org/springframework/docs/testing/resttestclient/workflow/RestClientWorkflowTests.java b/framework-docs/src/main/java/org/springframework/docs/testing/resttestclient/workflow/RestClientWorkflowTests.java new file mode 100644 index 00000000000..2f67b39156f --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/testing/resttestclient/workflow/RestClientWorkflowTests.java @@ -0,0 +1,85 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.docs.testing.resttestclient.workflow; + +import org.junit.jupiter.api.Test; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.client.EntityExchangeResult; +import org.springframework.test.web.servlet.client.RestTestClient; + +public class RestClientWorkflowTests { + + RestTestClient client; + + @Test + void workflowTest() { + // tag::test[] + client.get().uri("/persons/1") + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isOk() + .expectHeader().contentType(MediaType.APPLICATION_JSON) + .expectBody(); + // end::test[] + } + + @Test + void softAssertions() { + // tag::soft-assertions[] + client.get().uri("/persons/1") + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectAll( + spec -> spec.expectStatus().isOk(), + spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON) + ); + // end::soft-assertions[] + } + + @Test + void consumeWith() { + // tag::consume[] + client.get().uri("/persons/1") + .exchange() + .expectStatus().isOk() + .expectBody(Person.class) + .consumeWith(result -> { + // custom assertions (for example, AssertJ)... + }); + // end::consume[] + } + + @Test + void returnResult() { + // tag::result[] + EntityExchangeResult result = client.get().uri("/persons/1") + .exchange() + .expectStatus().isOk() + .expectBody(Person.class) + .returnResult(); + + Person person = result.getResponseBody(); + HttpHeaders requestHeaders = result.getRequestHeaders(); + // end::result[] + } + + record Person(String name) { + + } +} diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/testing/resttestclient/assertj/AssertJTests.kt b/framework-docs/src/main/kotlin/org/springframework/docs/testing/resttestclient/assertj/AssertJTests.kt new file mode 100644 index 00000000000..076c59f4e9f --- /dev/null +++ b/framework-docs/src/main/kotlin/org/springframework/docs/testing/resttestclient/assertj/AssertJTests.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.docs.testing.resttestclient.assertj + +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Test +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.client.RestTestClient +import org.springframework.test.web.servlet.client.assertj.RestTestClientResponse + +class AssertJTests { + + + lateinit var client: RestTestClient + + @Test + fun withSpec() { + // tag::withSpec[] + val spec = client.get().uri("/persons").exchange() + + val response = RestTestClientResponse.from(spec) + Assertions.assertThat(response).hasStatusOk() + Assertions.assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN) + // end::withSpec[] + } + + @Test + fun withResult() { + // tag::withResult[] + val result = client.get().uri("/persons").exchange().returnResult() + + val response = RestTestClientResponse.from(result) + Assertions.assertThat(response).hasStatusOk() + Assertions.assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN) + // end::withResult[] + } + +} diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/testing/resttestclient/contextconfig/RestClientContextTests.kt b/framework-docs/src/main/kotlin/org/springframework/docs/testing/resttestclient/contextconfig/RestClientContextTests.kt new file mode 100644 index 00000000000..956c34a1725 --- /dev/null +++ b/framework-docs/src/main/kotlin/org/springframework/docs/testing/resttestclient/contextconfig/RestClientContextTests.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.docs.testing.resttestclient.contextconfig + +import org.junit.jupiter.api.BeforeEach +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig +import org.springframework.test.web.servlet.client.RestTestClient +import org.springframework.web.context.WebApplicationContext + +@SpringJUnitConfig(WebConfig::class) // Specify the configuration to load +class RestClientContextTests { + + lateinit var client: RestTestClient + + @BeforeEach + fun setUp(context: WebApplicationContext) { // Inject the configuration + // Create the `RestTestClient` + client = RestTestClient.bindToApplicationContext(context).build() + } +} \ No newline at end of file diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/testing/resttestclient/contextconfig/WebConfig.kt b/framework-docs/src/main/kotlin/org/springframework/docs/testing/resttestclient/contextconfig/WebConfig.kt new file mode 100644 index 00000000000..5884e34c8da --- /dev/null +++ b/framework-docs/src/main/kotlin/org/springframework/docs/testing/resttestclient/contextconfig/WebConfig.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.docs.testing.resttestclient.contextconfig + +class WebConfig { +} \ No newline at end of file diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/testing/resttestclient/json/JsonTests.kt b/framework-docs/src/main/kotlin/org/springframework/docs/testing/resttestclient/json/JsonTests.kt new file mode 100644 index 00000000000..d7e935cb8c2 --- /dev/null +++ b/framework-docs/src/main/kotlin/org/springframework/docs/testing/resttestclient/json/JsonTests.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.docs.testing.resttestclient.json + +import org.junit.jupiter.api.Test +import org.springframework.test.web.servlet.client.RestTestClient + +class JsonTests { + + lateinit var client: RestTestClient + + @Test + fun jsonBody() { + // tag::jsonBody[] + client.get().uri("/persons/1") + .exchange() + .expectStatus().isOk() + .expectBody() + .json("{\"name\":\"Jane\"}") + // end::jsonBody[] + } + + @Test + fun jsonPath() { + // tag::jsonPath[] + client.get().uri("/persons") + .exchange() + .expectStatus().isOk() + .expectBody() + .jsonPath("$[0].name").isEqualTo("Jane") + .jsonPath("$[1].name").isEqualTo("Jason") + // end::jsonPath[] + } + +} \ No newline at end of file diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/testing/resttestclient/nocontent/NoContentTests.kt b/framework-docs/src/main/kotlin/org/springframework/docs/testing/resttestclient/nocontent/NoContentTests.kt new file mode 100644 index 00000000000..6fbbf22baa0 --- /dev/null +++ b/framework-docs/src/main/kotlin/org/springframework/docs/testing/resttestclient/nocontent/NoContentTests.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.docs.testing.resttestclient.nocontent + +import org.junit.jupiter.api.Test +import org.springframework.test.web.servlet.client.RestTestClient +import org.springframework.test.web.servlet.client.expectBody + +class NoContentTests { + + lateinit var client: RestTestClient + + @Test + fun emptyBody() { + val person = Person("Jane") + // tag::emptyBody[] + client.post().uri("/persons") + .body(person) + .exchange() + .expectStatus().isCreated() + .expectBody().isEmpty() + // end::emptyBody[] + } + + @Test + fun ignoreBody() { + val person = Person("Jane") + // tag::ignoreBody[] + client.get().uri("/persons/123") + .exchange() + .expectStatus().isNotFound + .expectBody() + // end::ignoreBody[] + } + + data class Person(val name: String) + +} \ No newline at end of file diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/testing/resttestclient/workflow/RestClientWorkflowTests.kt b/framework-docs/src/main/kotlin/org/springframework/docs/testing/resttestclient/workflow/RestClientWorkflowTests.kt new file mode 100644 index 00000000000..32a8b13d6a0 --- /dev/null +++ b/framework-docs/src/main/kotlin/org/springframework/docs/testing/resttestclient/workflow/RestClientWorkflowTests.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.docs.testing.resttestclient.workflow + +import org.junit.jupiter.api.Test +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.client.RestTestClient +import org.springframework.test.web.servlet.client.expectBody + +class RestClientWorkflowTests { + + lateinit var client: RestTestClient + + @Test + fun workflowTest() { + // tag::test[] + client.get().uri("/persons/1") + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isOk() + .expectHeader().contentType(MediaType.APPLICATION_JSON) + .expectBody() + // end::test[] + } + + @Test + fun softAssertions() { + // tag::soft-assertions[] + client.get().uri("/persons/1") + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectAll( + { spec -> spec.expectStatus().isOk() }, + { spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON) } + ) + // end::soft-assertions[] + } + + @Test + fun consumeWith() { + // tag::consume[] + client.get().uri("/persons/1") + .exchange() + .expectStatus().isOk() + .expectBody() + .consumeWith { + // custom assertions (for example, AssertJ)... + } + // end::consume[] + } + + @Test + fun returnResult() { + // tag::result[] + val result = client.get().uri("/persons/1") + .exchange() + .expectStatus().isOk + .expectBody() + .returnResult() + + val person: Person? = result.responseBody + val requestHeaders = result.responseHeaders + // end::result[] + } + + data class Person(val name: String) + +} diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java index 48bc2d37354..05654d676fe 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java @@ -702,7 +702,7 @@ public interface WebTestClient { S attributes(Consumer> attributesConsumer); /** - * Perform the exchange. + * Perform the HTTP exchange and . * @return a spec for expectations on the response */ ResponseSpec exchange();