diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc index 4c473a929e..d08ffbb58d 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc @@ -735,17 +735,21 @@ include::code:server/MyDataLdapTests[] [[features.testing.spring-boot-applications.autoconfigured-rest-client]] ==== Auto-configured REST Clients You can use the `@RestClientTest` annotation to test REST clients. -By default, it auto-configures Jackson, GSON, and Jsonb support, configures a `RestTemplateBuilder`, and adds support for `MockRestServiceServer`. +By default, it auto-configures Jackson, GSON, and Jsonb support, configures a `RestTemplateBuilder` and a `RestClient.Builder`, and adds support for `MockRestServiceServer`. Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@RestClientTest` annotation is used. `@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. TIP: A list of the auto-configuration settings that are enabled by `@RestClientTest` can be <>. -The specific beans that you want to test should be specified by using the `value` or `components` attribute of `@RestClientTest`, as shown in the following example: +The specific beans that you want to test should be specified by using the `value` or `components` attribute of `@RestClientTest`. -include::code:MyRestClientTests[] +When using a `RestTemplateBuilder` in the beans under test and `RestTemplateBuilder.rootUri(String rootUri)` has been called when building the `RestTemplate`, then the root URI should be omitted from the `MockRestServiceServer` expectations as shown in the following example: +include::code:MyRestTemplateServiceTests[] +When using a `RestClient.Builder` in the beans under test, or when using a `RestTemplateBuilder` without calling `rootUri(String rootURI)`, the full URI must be used in the `MockRestServiceServer` expectations as shown in the following example: + +include::code:MyRestClientServiceTests[] [[features.testing.spring-boot-applications.autoconfigured-spring-restdocs]] ==== Auto-configured Spring REST Docs Tests diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.java new file mode 100644 index 0000000000..8d8437fc8f --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2023 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.boot.docs.features.testing.springbootapplications.autoconfiguredrestclient; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.client.RestClientTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.MockRestServiceServer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +@RestClientTest(RemoteVehicleDetailsService.class) +class MyRestClientServiceTests { + + @Autowired + private RemoteVehicleDetailsService service; + + @Autowired + private MockRestServiceServer server; + + @Test + void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() { + this.server.expect(requestTo("https://example.com/greet/details")) + .andRespond(withSuccess("hello", MediaType.TEXT_PLAIN)); + String greeting = this.service.callRestService(); + assertThat(greeting).isEqualTo("hello"); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.java similarity index 94% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.java index 90a1a82c2e..fdf67b9621 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -28,7 +28,7 @@ import static org.springframework.test.web.client.match.MockRestRequestMatchers. import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; @RestClientTest(RemoteVehicleDetailsService.class) -class MyRestClientTests { +class MyRestTemplateServiceTests { @Autowired private RemoteVehicleDetailsService service; diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.kt new file mode 100644 index 0000000000..d226475760 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2023 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.boot.docs.features.testing.springbootapplications.autoconfiguredrestclient + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.client.RestClientTest +import org.springframework.http.MediaType +import org.springframework.test.web.client.MockRestServiceServer +import org.springframework.test.web.client.match.MockRestRequestMatchers +import org.springframework.test.web.client.response.MockRestResponseCreators + +@RestClientTest(RemoteVehicleDetailsService::class) +class MyRestClientServiceTests( + @Autowired val service: RemoteVehicleDetailsService, + @Autowired val server: MockRestServiceServer) { + + @Test + fun getVehicleDetailsWhenResultIsSuccessShouldReturnDetails(): Unit { + server.expect(MockRestRequestMatchers.requestTo("https://example.com/greet/details")) + .andRespond(MockRestResponseCreators.withSuccess("hello", MediaType.TEXT_PLAIN)) + val greeting = service.callRestService() + assertThat(greeting).isEqualTo("hello") + } + +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.kt similarity index 94% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.kt index 3345aa5617..03f1a78cc9 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -26,7 +26,7 @@ import org.springframework.test.web.client.match.MockRestRequestMatchers import org.springframework.test.web.client.response.MockRestResponseCreators @RestClientTest(RemoteVehicleDetailsService::class) -class MyRestClientTests( +class MyRestTemplateServiceTests( @Autowired val service: RemoteVehicleDetailsService, @Autowired val server: MockRestServiceServer) { diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServer.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServer.java index 6e9e8def96..43e1aac70e 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServer.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 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. @@ -25,20 +25,26 @@ import java.lang.annotation.Target; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.autoconfigure.properties.PropertyMapping; +import org.springframework.boot.test.web.client.MockServerRestClientCustomizer; import org.springframework.boot.test.web.client.MockServerRestTemplateCustomizer; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClient; /** * Annotation that can be applied to a test class to enable and configure * auto-configuration of a single {@link MockRestServiceServer}. Only useful when a single - * call is made to {@link RestTemplateBuilder}. If multiple - * {@link org.springframework.web.client.RestTemplate RestTemplates} are in use, inject + * call is made to {@link RestTemplateBuilder} or {@link RestClient.Builder}. If multiple + * {@link org.springframework.web.client.RestTemplate RestTemplates} or + * {@link org.springframework.web.client.RestClient RestClients} are in use, inject a * {@link MockServerRestTemplateCustomizer} and use * {@link MockServerRestTemplateCustomizer#getServer(org.springframework.web.client.RestTemplate) - * getServer(RestTemplate)} or bind a {@link MockRestServiceServer} directly. + * getServer(RestTemplate)}, or inject a {@link MockServerRestClientCustomizer} and use + * {@link MockServerRestClientCustomizer#getServer(org.springframework.web.client.RestClient.Builder) + * * getServer(RestClient.Builder)}, or bind a {@link MockRestServiceServer} directly. * * @author Phillip Webb + * @author Scott Frederick * @since 1.4.0 * @see MockServerRestTemplateCustomizer */ @@ -51,7 +57,8 @@ import org.springframework.test.web.client.MockRestServiceServer; public @interface AutoConfigureMockRestServiceServer { /** - * If {@link MockServerRestTemplateCustomizer} should be enabled and + * If {@link MockServerRestTemplateCustomizer} and + * {@link MockServerRestClientCustomizer} should be enabled and * {@link MockRestServiceServer} beans should be registered. Defaults to {@code true} * @return if mock support is enabled */ diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/MockRestServiceServerAutoConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/MockRestServiceServerAutoConfiguration.java index aa76319d00..6aba6245a0 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/MockRestServiceServerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/MockRestServiceServerAutoConfiguration.java @@ -19,10 +19,12 @@ package org.springframework.boot.test.autoconfigure.web.client; import java.io.IOException; import java.lang.reflect.Constructor; import java.time.Duration; +import java.util.Collection; import java.util.Map; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.test.web.client.MockServerRestClientCustomizer; import org.springframework.boot.test.web.client.MockServerRestTemplateCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.http.client.ClientHttpRequest; @@ -33,12 +35,14 @@ import org.springframework.test.web.client.RequestExpectationManager; import org.springframework.test.web.client.RequestMatcher; import org.springframework.test.web.client.ResponseActions; import org.springframework.util.Assert; +import org.springframework.web.client.RestClient; import org.springframework.web.client.RestTemplate; /** * Auto-configuration for {@link MockRestServiceServer} support. * * @author Phillip Webb + * @author Scott Frederick * @since 1.4.0 * @see AutoConfigureMockRestServiceServer */ @@ -52,21 +56,29 @@ public class MockRestServiceServerAutoConfiguration { } @Bean - public MockRestServiceServer mockRestServiceServer(MockServerRestTemplateCustomizer customizer) { + public MockServerRestClientCustomizer mockServerRestClientCustomizer() { + return new MockServerRestClientCustomizer(); + } + + @Bean + public MockRestServiceServer mockRestServiceServer(MockServerRestTemplateCustomizer restTemplateCustomizer, + MockServerRestClientCustomizer restClientCustomizer) { try { - return createDeferredMockRestServiceServer(customizer); + return createDeferredMockRestServiceServer(restTemplateCustomizer, restClientCustomizer); } catch (Exception ex) { throw new IllegalStateException(ex); } } - private MockRestServiceServer createDeferredMockRestServiceServer(MockServerRestTemplateCustomizer customizer) - throws Exception { + private MockRestServiceServer createDeferredMockRestServiceServer( + MockServerRestTemplateCustomizer restTemplateCustomizer, + MockServerRestClientCustomizer restClientCustomizer) throws Exception { Constructor constructor = MockRestServiceServer.class .getDeclaredConstructor(RequestExpectationManager.class); constructor.setAccessible(true); - return constructor.newInstance(new DeferredRequestExpectationManager(customizer)); + return constructor + .newInstance(new DeferredRequestExpectationManager(restTemplateCustomizer, restClientCustomizer)); } /** @@ -77,10 +89,14 @@ public class MockRestServiceServerAutoConfiguration { */ private static class DeferredRequestExpectationManager implements RequestExpectationManager { - private final MockServerRestTemplateCustomizer customizer; + private final MockServerRestTemplateCustomizer restTemplateCustomizer; + + private final MockServerRestClientCustomizer restClientCustomizer; - DeferredRequestExpectationManager(MockServerRestTemplateCustomizer customizer) { - this.customizer = customizer; + DeferredRequestExpectationManager(MockServerRestTemplateCustomizer restTemplateCustomizer, + MockServerRestClientCustomizer restClientCustomizer) { + this.restTemplateCustomizer = restTemplateCustomizer; + this.restClientCustomizer = restClientCustomizer; } @Override @@ -105,19 +121,34 @@ public class MockRestServiceServerAutoConfiguration { @Override public void reset() { - Map expectationManagers = this.customizer.getExpectationManagers(); + resetExpectations(this.restTemplateCustomizer.getExpectationManagers().values()); + resetExpectations(this.restClientCustomizer.getExpectationManagers().values()); + } + + private void resetExpectations(Collection expectationManagers) { if (expectationManagers.size() == 1) { - getDelegate().reset(); + expectationManagers.iterator().next().reset(); } } private RequestExpectationManager getDelegate() { - Map expectationManagers = this.customizer.getExpectationManagers(); - Assert.state(!expectationManagers.isEmpty(), "Unable to use auto-configured MockRestServiceServer since " - + "MockServerRestTemplateCustomizer has not been bound to a RestTemplate"); - Assert.state(expectationManagers.size() == 1, "Unable to use auto-configured MockRestServiceServer since " - + "MockServerRestTemplateCustomizer has been bound to more than one RestTemplate"); - return expectationManagers.values().iterator().next(); + Map restTemplateExpectationManagers = this.restTemplateCustomizer + .getExpectationManagers(); + Map restClientExpectationManagers = this.restClientCustomizer + .getExpectationManagers(); + Assert.state(!(restTemplateExpectationManagers.isEmpty() && restClientExpectationManagers.isEmpty()), + "Unable to use auto-configured MockRestServiceServer since " + + "a mock server customizer has not been bound to a RestTemplate or RestClient"); + if (!restTemplateExpectationManagers.isEmpty()) { + Assert.state(restTemplateExpectationManagers.size() == 1, + "Unable to use auto-configured MockRestServiceServer since " + + "MockServerRestTemplateCustomizer has been bound to more than one RestTemplate"); + return restTemplateExpectationManagers.values().iterator().next(); + } + Assert.state(restClientExpectationManagers.size() == 1, + "Unable to use auto-configured MockRestServiceServer since " + + "MockServerRestClientCustomizer has been bound to more than one RestClient"); + return restClientExpectationManagers.values().iterator().next(); } } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTest.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTest.java index eba5ed69d0..8a93b1b422 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTest.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 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. @@ -38,11 +38,12 @@ import org.springframework.stereotype.Component; import org.springframework.test.context.BootstrapWith; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClient; import org.springframework.web.client.RestTemplate; /** * Annotation for a Spring rest client test that focuses only on beans - * that use {@link RestTemplateBuilder}. + * that use {@link RestTemplateBuilder} or {@link RestClient.Builder}. *

* Using this annotation will disable full auto-configuration and instead apply only * configuration relevant to rest client tests (i.e. Jackson or GSON auto-configuration diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient.imports b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient.imports index c789b0b5c2..cad2d5fb96 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient.imports +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient.imports @@ -6,4 +6,5 @@ org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration +org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration \ No newline at end of file diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestClientService.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestClientService.java new file mode 100644 index 0000000000..29d459100f --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestClientService.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2023 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.boot.test.autoconfigure.web.client; + +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClient.Builder; + +/** + * A second example web client used with {@link RestClientTest @RestClientTest} tests. + * + * @author Scott Frederick + */ +@Service +public class AnotherExampleRestClientService { + + private final Builder builder; + + private final RestClient restClient; + + public AnotherExampleRestClientService(RestClient.Builder builder) { + this.builder = builder; + this.restClient = builder.baseUrl("https://example.com").build(); + } + + protected Builder getRestClientBuilder() { + return this.builder; + } + + public String test() { + return this.restClient.get().uri("/test").retrieve().toEntity(String.class).getBody(); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestClient.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestTemplateService.java similarity index 87% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestClient.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestTemplateService.java index e7452ca904..a781d7cc80 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestClient.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestTemplateService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2023 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. @@ -26,11 +26,11 @@ import org.springframework.web.client.RestTemplate; * @author Phillip Webb */ @Service -public class AnotherExampleRestClient { +public class AnotherExampleRestTemplateService { private final RestTemplate restTemplate; - public AnotherExampleRestClient(RestTemplateBuilder builder) { + public AnotherExampleRestTemplateService(RestTemplateBuilder builder) { this.restTemplate = builder.rootUri("https://example.com").build(); } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerEnabledFalseIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerEnabledFalseIntegrationTests.java index 68c31ee5bd..fc55c4ad99 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerEnabledFalseIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerEnabledFalseIntegrationTests.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.web.client.MockServerRestClientCustomizer; import org.springframework.boot.test.web.client.MockServerRestTemplateCustomizer; import org.springframework.context.ApplicationContext; @@ -43,6 +44,8 @@ class AutoConfigureMockRestServiceServerEnabledFalseIntegrationTests { void mockServerRestTemplateCustomizerShouldNotBeRegistered() { assertThatExceptionOfType(NoSuchBeanDefinitionException.class) .isThrownBy(() -> this.applicationContext.getBean(MockServerRestTemplateCustomizer.class)); + assertThatExceptionOfType(NoSuchBeanDefinitionException.class) + .isThrownBy(() -> this.applicationContext.getBean(MockServerRestClientCustomizer.class)); } } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerWithRestClientIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerWithRestClientIntegrationTests.java new file mode 100644 index 0000000000..7e7b5adcbd --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerWithRestClientIntegrationTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2012-2023 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.boot.test.autoconfigure.web.client; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClient.Builder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for + * {@link AutoConfigureMockRestServiceServer @AutoConfigureMockRestServiceServer} with a + * {@link RestClient} configured with a base URL. + * + * @author Scott Frederick + */ +@SpringBootTest +@AutoConfigureMockRestServiceServer +class AutoConfigureMockRestServiceServerWithRestClientIntegrationTests { + + @Autowired + private RestClient restClient; + + @Autowired + private MockRestServiceServer server; + + @Test + void mockServerExpectationsAreMatched() { + this.server.expect(requestTo("/rest/test")).andRespond(withSuccess("hello", MediaType.TEXT_HTML)); + ResponseEntity entity = this.restClient.get().uri("/test").retrieve().toEntity(String.class); + assertThat(entity.getBody()).isEqualTo("hello"); + } + + @EnableAutoConfiguration(exclude = CassandraAutoConfiguration.class) + @Configuration(proxyBeanMethods = false) + static class RootUriConfiguration { + + @Bean + RestClient restClient(Builder restClientBuilder) { + return restClientBuilder.baseUrl("/rest").build(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerWithRootUriIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerWithRestTemplateRootUriIntegrationTests.java similarity index 95% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerWithRootUriIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerWithRestTemplateRootUriIntegrationTests.java index 2ac9b41f41..fabdbf9602 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerWithRootUriIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerWithRestTemplateRootUriIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -44,7 +44,7 @@ import static org.springframework.test.web.client.response.MockRestResponseCreat */ @SpringBootTest @AutoConfigureMockRestServiceServer -class AutoConfigureMockRestServiceServerWithRootUriIntegrationTests { +class AutoConfigureMockRestServiceServerWithRestTemplateRootUriIntegrationTests { @Autowired private RestTemplate restTemplate; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestClientService.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestClientService.java new file mode 100644 index 0000000000..f4e4c922a8 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestClientService.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2023 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.boot.test.autoconfigure.web.client; + +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClient.Builder; + +/** + * Example web client using {@code RestClient} with {@link RestClientTest @RestClientTest} + * tests. + * + * @author Scott Frederick + */ +@Service +public class ExampleRestClientService { + + private final Builder builder; + + private final RestClient restClient; + + public ExampleRestClientService(RestClient.Builder builder) { + this.builder = builder; + this.restClient = builder.baseUrl("https://example.com").build(); + } + + protected Builder getRestClientBuilder() { + return this.builder; + } + + public String test() { + return this.restClient.get().uri("/test").retrieve().toEntity(String.class).getBody(); + } + + public void testPostWithBody(String body) { + this.restClient.post().uri("/test").body(body).retrieve().toBodilessEntity(); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestClient.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestTemplateService.java similarity index 82% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestClient.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestTemplateService.java index 06b923f7ac..95f5521175 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestClient.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestTemplateService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -21,16 +21,17 @@ import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; /** - * Example web client used with {@link RestClientTest @RestClientTest} tests. + * Example web client using {@code RestTemplate} with + * {@link RestClientTest @RestClientTest} tests. * * @author Phillip Webb */ @Service -public class ExampleRestClient { +public class ExampleRestTemplateService { private final RestTemplate restTemplate; - public ExampleRestClient(RestTemplateBuilder builder) { + public ExampleRestTemplateService(RestTemplateBuilder builder) { this.restTemplate = builder.rootUri("https://example.com").build(); } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestNoComponentIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestNoComponentIntegrationTests.java index 6ed3e63e46..3da4654a6b 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestNoComponentIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestNoComponentIntegrationTests.java @@ -50,7 +50,7 @@ class RestClientTestNoComponentIntegrationTests { @Test void exampleRestClientIsNotInjected() { assertThatExceptionOfType(NoSuchBeanDefinitionException.class) - .isThrownBy(() -> this.applicationContext.getBean(ExampleRestClient.class)); + .isThrownBy(() -> this.applicationContext.getBean(ExampleRestTemplateService.class)); } @Test @@ -61,7 +61,7 @@ class RestClientTestNoComponentIntegrationTests { @Test void manuallyCreateBean() { - ExampleRestClient client = new ExampleRestClient(this.restTemplateBuilder); + ExampleRestTemplateService client = new ExampleRestTemplateService(this.restTemplateBuilder); this.server.expect(requestTo("/test")).andRespond(withSuccess("hello", MediaType.TEXT_HTML)); assertThat(client.test()).isEqualTo("hello"); } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestClientIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestClientIntegrationTests.java new file mode 100644 index 0000000000..098f6456a9 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestClientIntegrationTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2023 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.boot.test.autoconfigure.web.client; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClient; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.content; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for {@link RestClientTest @RestClientTest} with a {@link RestClient}. + * + * @author Scott Frederick + */ +@RestClientTest(ExampleRestClientService.class) +class RestClientTestRestClientIntegrationTests { + + @Autowired + private MockRestServiceServer server; + + @Autowired + private ExampleRestClientService client; + + @Test + void mockServerCall1() { + this.server.expect(requestTo(uri("/test"))).andRespond(withSuccess("1", MediaType.TEXT_HTML)); + assertThat(this.client.test()).isEqualTo("1"); + } + + @Test + void mockServerCall2() { + this.server.expect(requestTo(uri("/test"))).andRespond(withSuccess("2", MediaType.TEXT_HTML)); + assertThat(this.client.test()).isEqualTo("2"); + } + + @Test + void mockServerCallWithContent() { + this.server.expect(requestTo(uri("/test"))) + .andExpect(content().string("test")) + .andRespond(withSuccess("1", MediaType.TEXT_HTML)); + this.client.testPostWithBody("test"); + } + + private static String uri(String path) { + return "https://example.com" + path; + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestClientTwoComponentsIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestClientTwoComponentsIntegrationTests.java new file mode 100644 index 0000000000..15695607fe --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestClientTwoComponentsIntegrationTests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2023 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.boot.test.autoconfigure.web.client; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.web.client.MockServerRestClientCustomizer; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.MockRestServiceServer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for {@link RestClientTest @RestClientTest} with two {@code RestClient} clients. + * + * @author Phillip Webb + * @author Scott Frederick + */ +@RestClientTest({ ExampleRestClientService.class, AnotherExampleRestClientService.class }) +class RestClientTestRestClientTwoComponentsIntegrationTests { + + @Autowired + private ExampleRestClientService client1; + + @Autowired + private AnotherExampleRestClientService client2; + + @Autowired + private MockServerRestClientCustomizer customizer; + + @Autowired + private MockRestServiceServer server; + + @Test + void serverShouldNotWork() { + assertThatIllegalStateException().isThrownBy( + () -> this.server.expect(requestTo(uri("/test"))).andRespond(withSuccess("hello", MediaType.TEXT_HTML))) + .withMessageContaining("Unable to use auto-configured"); + } + + @Test + void client1RestCallViaCustomizer() { + this.customizer.getServer(this.client1.getRestClientBuilder()) + .expect(requestTo(uri("/test"))) + .andRespond(withSuccess("hello", MediaType.TEXT_HTML)); + assertThat(this.client1.test()).isEqualTo("hello"); + } + + @Test + void client2RestCallViaCustomizer() { + this.customizer.getServer(this.client2.getRestClientBuilder()) + .expect(requestTo(uri("/test"))) + .andRespond(withSuccess("there", MediaType.TEXT_HTML)); + assertThat(this.client2.test()).isEqualTo("there"); + } + + private static String uri(String path) { + return "https://example.com" + path; + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientRestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestTemplateIntegrationTests.java similarity index 93% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientRestIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestTemplateIntegrationTests.java index 3966b197e2..7f92f75500 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientRestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestTemplateIntegrationTests.java @@ -32,14 +32,14 @@ import static org.springframework.test.web.client.response.MockRestResponseCreat * * @author Phillip Webb */ -@RestClientTest(ExampleRestClient.class) -class RestClientRestIntegrationTests { +@RestClientTest(ExampleRestTemplateService.class) +class RestClientTestRestTemplateIntegrationTests { @Autowired private MockRestServiceServer server; @Autowired - private ExampleRestClient client; + private ExampleRestTemplateService client; @Test void mockServerCall1() { diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestTwoComponentsIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestTemplateTwoComponentsIntegrationTests.java similarity index 86% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestTwoComponentsIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestTemplateTwoComponentsIntegrationTests.java index 08e00159c6..c106df3776 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestTwoComponentsIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestRestTemplateTwoComponentsIntegrationTests.java @@ -29,18 +29,18 @@ import static org.springframework.test.web.client.match.MockRestRequestMatchers. import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; /** - * Tests for {@link RestClientTest @RestClientTest} with two clients. + * Tests for {@link RestClientTest @RestClientTest} with two {@code RestTemplate} clients. * * @author Phillip Webb */ -@RestClientTest({ ExampleRestClient.class, AnotherExampleRestClient.class }) -class RestClientTestTwoComponentsIntegrationTests { +@RestClientTest({ ExampleRestTemplateService.class, AnotherExampleRestTemplateService.class }) +class RestClientTestRestTemplateTwoComponentsIntegrationTests { @Autowired - private ExampleRestClient client1; + private ExampleRestTemplateService client1; @Autowired - private AnotherExampleRestClient client2; + private AnotherExampleRestTemplateService client2; @Autowired private MockServerRestTemplateCustomizer customizer; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithRestClientComponentIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithRestClientComponentIntegrationTests.java new file mode 100644 index 0000000000..954ecfbb1b --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithRestClientComponentIntegrationTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2023 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.boot.test.autoconfigure.web.client; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.MockRestServiceServer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for {@link RestClientTest @RestClientTest} with a single client using + * {@code RestClient}. + * + * @author Phillip Webb + * @author Scott Frederick + */ +@RestClientTest(ExampleRestClientService.class) +class RestClientTestWithRestClientComponentIntegrationTests { + + @Autowired + private MockRestServiceServer server; + + @Autowired + private ExampleRestClientService client; + + @Test + void mockServerCall() { + this.server.expect(requestTo("https://example.com/test")).andRespond(withSuccess("hello", MediaType.TEXT_HTML)); + assertThat(this.client.test()).isEqualTo("hello"); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithComponentIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithRestTemplateComponentIntegrationTests.java similarity index 85% rename from spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithComponentIntegrationTests.java rename to spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithRestTemplateComponentIntegrationTests.java index 09ba31463e..f495765f18 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithComponentIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithRestTemplateComponentIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 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. @@ -27,18 +27,19 @@ import static org.springframework.test.web.client.match.MockRestRequestMatchers. import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; /** - * Tests for {@link RestClientTest @RestClientTest} with a single client. + * Tests for {@link RestClientTest @RestClientTest} with a single client using + * {@code RestTemplate}. * * @author Phillip Webb */ -@RestClientTest(ExampleRestClient.class) -class RestClientTestWithComponentIntegrationTests { +@RestClientTest(ExampleRestTemplateService.class) +class RestClientTestWithRestTemplateComponentIntegrationTests { @Autowired private MockRestServiceServer server; @Autowired - private ExampleRestClient client; + private ExampleRestTemplateService client; @Test void mockServerCall() { diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithoutJacksonIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithoutJacksonIntegrationTests.java index b6aa993467..250f50f5d1 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithoutJacksonIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithoutJacksonIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 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. @@ -34,14 +34,14 @@ import static org.springframework.test.web.client.response.MockRestResponseCreat * @author Andy Wilkinson */ @ClassPathExclusions("jackson-*.jar") -@RestClientTest(ExampleRestClient.class) +@RestClientTest(ExampleRestTemplateService.class) class RestClientTestWithoutJacksonIntegrationTests { @Autowired private MockRestServiceServer server; @Autowired - private ExampleRestClient client; + private ExampleRestTemplateService client; @Test void restClientTestCanBeUsedWhenJacksonIsNotOnTheClassPath() { diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/MockServerRestClientCustomizer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/MockServerRestClientCustomizer.java new file mode 100644 index 0000000000..29b8345140 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/MockServerRestClientCustomizer.java @@ -0,0 +1,139 @@ +/* + * Copyright 2012-2023 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.boot.test.web.client; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +import org.springframework.beans.BeanUtils; +import org.springframework.boot.web.client.RestClientCustomizer; +import org.springframework.http.client.BufferingClientHttpRequestFactory; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.test.web.client.MockRestServiceServer.MockRestServiceServerBuilder; +import org.springframework.test.web.client.RequestExpectationManager; +import org.springframework.test.web.client.SimpleRequestExpectationManager; +import org.springframework.util.Assert; +import org.springframework.web.client.RestClient; + +/** + * {@link RestClientCustomizer} that can be applied to {@link RestClient.Builder} + * instances to add {@link MockRestServiceServer} support. + *

+ * Typically applied to an existing builder before it is used, for example: + *

+ * MockServerRestClientCustomizer customizer = new MockServerRestClientCustomizer();
+ * RestClient.Builder builder = RestClient.builder();
+ * customizer.customize(builder);
+ * MyBean bean = new MyBean(client.build());
+ * customizer.getServer().expect(requestTo("/hello")).andRespond(withSuccess());
+ * bean.makeRestCall();
+ * 
+ *

+ * If the customizer is only used once, the {@link #getServer()} method can be used to + * obtain the mock server. If the customizer has been used more than once the + * {@link #getServer(RestClient.Builder)} or {@link #getServers()} method must be used to + * access the related server. + * + * @author Scott Frederick + * @since 3.2.0 + * @see #getServer() + * @see #getServer(RestClient.Builder) + */ +public class MockServerRestClientCustomizer implements RestClientCustomizer { + + private final Map expectationManagers = new ConcurrentHashMap<>(); + + private final Map servers = new ConcurrentHashMap<>(); + + private final Supplier expectationManagerSupplier; + + private boolean bufferContent = false; + + public MockServerRestClientCustomizer() { + this(SimpleRequestExpectationManager::new); + } + + /** + * Crate a new {@link MockServerRestClientCustomizer} instance. + * @param expectationManager the expectation manager class to use + */ + public MockServerRestClientCustomizer(Class expectationManager) { + this(() -> BeanUtils.instantiateClass(expectationManager)); + Assert.notNull(expectationManager, "ExpectationManager must not be null"); + } + + /** + * Crate a new {@link MockServerRestClientCustomizer} instance. + * @param expectationManagerSupplier a supplier that provides the + * {@link RequestExpectationManager} to use + * @since 3.0.0 + */ + public MockServerRestClientCustomizer(Supplier expectationManagerSupplier) { + Assert.notNull(expectationManagerSupplier, "ExpectationManagerSupplier must not be null"); + this.expectationManagerSupplier = expectationManagerSupplier; + } + + /** + * Set if the {@link BufferingClientHttpRequestFactory} wrapper should be used to + * buffer the input and output streams, and for example, allow multiple reads of the + * response body. + * @param bufferContent if request and response content should be buffered + * @since 3.1.0 + */ + public void setBufferContent(boolean bufferContent) { + this.bufferContent = bufferContent; + } + + @Override + public void customize(RestClient.Builder restClientBuilder) { + RequestExpectationManager expectationManager = createExpectationManager(); + MockRestServiceServerBuilder serverBuilder = MockRestServiceServer.bindTo(restClientBuilder); + if (this.bufferContent) { + serverBuilder.bufferContent(); + } + MockRestServiceServer server = serverBuilder.build(expectationManager); + this.expectationManagers.put(restClientBuilder, expectationManager); + this.servers.put(restClientBuilder, server); + } + + protected RequestExpectationManager createExpectationManager() { + return this.expectationManagerSupplier.get(); + } + + public MockRestServiceServer getServer() { + Assert.state(!this.servers.isEmpty(), "Unable to return a single MockRestServiceServer since " + + "MockServerRestClientCustomizer has not been bound to a RestClient"); + Assert.state(this.servers.size() == 1, "Unable to return a single MockRestServiceServer since " + + "MockServerRestClientCustomizer has been bound to more than one RestClient"); + return this.servers.values().iterator().next(); + } + + public Map getExpectationManagers() { + return this.expectationManagers; + } + + public MockRestServiceServer getServer(RestClient.Builder restClientBuilder) { + return this.servers.get(restClientBuilder); + } + + public Map getServers() { + return Collections.unmodifiableMap(this.servers); + } + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/MockServerRestClientCustomizerTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/MockServerRestClientCustomizerTests.java new file mode 100644 index 0000000000..0dffb76514 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/MockServerRestClientCustomizerTests.java @@ -0,0 +1,163 @@ +/* + * Copyright 2012-2023 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.boot.test.web.client; + +import java.util.function.Supplier; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.test.web.client.RequestExpectationManager; +import org.springframework.test.web.client.SimpleRequestExpectationManager; +import org.springframework.test.web.client.UnorderedRequestExpectationManager; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClient.Builder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for {@link MockServerRestClientCustomizer}. + * + * @author Scott Frederick + */ +class MockServerRestClientCustomizerTests { + + private MockServerRestClientCustomizer customizer; + + @BeforeEach + void setup() { + this.customizer = new MockServerRestClientCustomizer(); + } + + @Test + void createShouldUseSimpleRequestExpectationManager() { + MockServerRestClientCustomizer customizer = new MockServerRestClientCustomizer(); + customizer.customize(RestClient.builder()); + assertThat(customizer.getServer()).extracting("expectationManager") + .isInstanceOf(SimpleRequestExpectationManager.class); + } + + @Test + void createWhenExpectationManagerClassIsNullShouldThrowException() { + Class expectationManager = null; + assertThatIllegalArgumentException().isThrownBy(() -> new MockServerRestClientCustomizer(expectationManager)) + .withMessageContaining("ExpectationManager must not be null"); + } + + @Test + void createWhenExpectationManagerSupplierIsNullShouldThrowException() { + Supplier expectationManagerSupplier = null; + assertThatIllegalArgumentException() + .isThrownBy(() -> new MockServerRestClientCustomizer(expectationManagerSupplier)) + .withMessageContaining("ExpectationManagerSupplier must not be null"); + } + + @Test + void createShouldUseExpectationManagerClass() { + MockServerRestClientCustomizer customizer = new MockServerRestClientCustomizer( + UnorderedRequestExpectationManager.class); + customizer.customize(RestClient.builder()); + assertThat(customizer.getServer()).extracting("expectationManager") + .isInstanceOf(UnorderedRequestExpectationManager.class); + } + + @Test + void createShouldUseSupplier() { + MockServerRestClientCustomizer customizer = new MockServerRestClientCustomizer( + UnorderedRequestExpectationManager::new); + customizer.customize(RestClient.builder()); + assertThat(customizer.getServer()).extracting("expectationManager") + .isInstanceOf(UnorderedRequestExpectationManager.class); + } + + @Test + void customizeShouldBindServer() { + Builder builder = RestClient.builder(); + this.customizer.customize(builder); + this.customizer.getServer().expect(requestTo("/test")).andRespond(withSuccess()); + builder.build().get().uri("/test").retrieve().toEntity(String.class); + this.customizer.getServer().verify(); + } + + @Test + void getServerWhenNoServersAreBoundShouldThrowException() { + assertThatIllegalStateException().isThrownBy(this.customizer::getServer) + .withMessageContaining("Unable to return a single MockRestServiceServer since " + + "MockServerRestClientCustomizer has not been bound to a RestClient"); + } + + @Test + void getServerWhenMultipleServersAreBoundShouldThrowException() { + this.customizer.customize(RestClient.builder()); + this.customizer.customize(RestClient.builder()); + assertThatIllegalStateException().isThrownBy(this.customizer::getServer) + .withMessageContaining("Unable to return a single MockRestServiceServer since " + + "MockServerRestClientCustomizer has been bound to more than one RestClient"); + } + + @Test + void getServerWhenSingleServerIsBoundShouldReturnServer() { + Builder builder = RestClient.builder(); + this.customizer.customize(builder); + assertThat(this.customizer.getServer()).isEqualTo(this.customizer.getServer(builder)); + } + + @Test + void getServerWhenRestClientBuilderIsFoundShouldReturnServer() { + Builder builder1 = RestClient.builder(); + Builder builder2 = RestClient.builder(); + this.customizer.customize(builder1); + this.customizer.customize(builder2); + assertThat(this.customizer.getServer(builder1)).isNotNull(); + assertThat(this.customizer.getServer(builder2)).isNotNull().isNotSameAs(this.customizer.getServer(builder1)); + } + + @Test + void getServerWhenRestClientBuilderIsNotFoundShouldReturnNull() { + Builder builder1 = RestClient.builder(); + Builder builder2 = RestClient.builder(); + this.customizer.customize(builder1); + assertThat(this.customizer.getServer(builder1)).isNotNull(); + assertThat(this.customizer.getServer(builder2)).isNull(); + } + + @Test + void getServersShouldReturnServers() { + Builder builder1 = RestClient.builder(); + Builder builder2 = RestClient.builder(); + this.customizer.customize(builder1); + this.customizer.customize(builder2); + assertThat(this.customizer.getServers()).containsOnlyKeys(builder1, builder2); + } + + @Test + void getExpectationManagersShouldReturnExpectationManagers() { + Builder builder1 = RestClient.builder(); + Builder builder2 = RestClient.builder(); + this.customizer.customize(builder1); + this.customizer.customize(builder2); + RequestExpectationManager manager1 = this.customizer.getExpectationManagers().get(builder1); + RequestExpectationManager manager2 = this.customizer.getExpectationManagers().get(builder2); + assertThat(this.customizer.getServer(builder1)).extracting("expectationManager").isEqualTo(manager1); + assertThat(this.customizer.getServer(builder2)).extracting("expectationManager").isEqualTo(manager2); + } + +}