diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServer.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServer.java new file mode 100644 index 0000000000..66867bb6ff --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServer.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2016 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 + * + * http://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 java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +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.MockServerRestTemplateCustomizer; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.test.web.client.MockRestServiceServer; + +/** + * 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 + * {@link MockServerRestTemplateCustomizer} and use + * {@link MockServerRestTemplateCustomizer#getServer(org.springframework.web.client.RestTemplate) + * getServer(RestTemplate)} or bind a {@link MockRestServiceServer} directly. + * + * @author Phillip Webb + * @since 1.4.0 + * @see MockServerRestTemplateCustomizer + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ImportAutoConfiguration +@PropertyMapping("spring.test.webclient.mockrestserviceserver") +public @interface AutoConfigureMockRestServiceServer { + + /** + * If {@link MockServerRestTemplateCustomizer} should be enabled and + * {@link MockRestServiceServer} beans should be registered. Defaults to {@code true} + * @return if mock support is enabled + */ + boolean enabled() default true; + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureWebClient.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureWebClient.java new file mode 100644 index 0000000000..8ea63db86b --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureWebClient.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2016 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 + * + * http://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 java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.test.autoconfigure.properties.PropertyMapping; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.web.client.RestTemplate; + +/** + * Annotation that can be applied to a test class to enable and configure + * auto-configuration of web clients. + * + * @author Stephane Nicoll + * @author Phillip Webb + * @since 1.4.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ImportAutoConfiguration +@PropertyMapping("spring.test.webclient") +public @interface AutoConfigureWebClient { + + /** + * If a {@link RestTemplate} bean should be registered. Defaults to {@code false} with + * the assumption that the {@link RestTemplateBuilder} will be used. + * @return if a {@link RestTemplate} bean should be added. + */ + boolean registerRestTemplate() default false; + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/MockRestServiceServerAutoConfiguration.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/MockRestServiceServerAutoConfiguration.java new file mode 100644 index 0000000000..ffad5c12fb --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/MockRestServiceServerAutoConfiguration.java @@ -0,0 +1,128 @@ +/* + * Copyright 2012-2016 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 + * + * http://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 java.io.IOException; +import java.lang.reflect.Constructor; +import java.util.Map; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.test.web.client.MockServerRestTemplateCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.test.web.client.ExpectedCount; +import org.springframework.test.web.client.MockRestServiceServer; +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.RestTemplate; + +/** + * Auto-configuration for {@link MockRestServiceServer} support. + * + * @author Phillip Webb + * @see AutoConfigureMockRestServiceServer + */ +@Configuration +@ConditionalOnProperty(prefix = "spring.test.webclient.mockrestserviceserver", name = "enabled") +class MockRestServiceServerAutoConfiguration { + + @Bean + public MockServerRestTemplateCustomizer mockServerRestTemplateCustomizer() { + return new MockServerRestTemplateCustomizer(); + } + + @Bean + public MockRestServiceServer mockRestServiceServer( + MockServerRestTemplateCustomizer customizer) { + try { + return createDeferredMockRestServiceServer(customizer); + } + catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + + private MockRestServiceServer createDeferredMockRestServiceServer( + MockServerRestTemplateCustomizer customizer) throws Exception { + Constructor constructor = MockRestServiceServer.class + .getDeclaredConstructor(RequestExpectationManager.class); + constructor.setAccessible(true); + return constructor.newInstance(new DeferredRequestExpectationManager(customizer)); + } + + /** + * {@link RequestExpectationManager} with the injected {@link MockRestServiceServer} + * so that the bean can be created before the + * {@link MockServerRestTemplateCustomizer#customize(RestTemplate) + * MockServerRestTemplateCustomizer} has been called. + */ + private static class DeferredRequestExpectationManager + implements RequestExpectationManager { + + private MockServerRestTemplateCustomizer customizer; + + DeferredRequestExpectationManager(MockServerRestTemplateCustomizer customizer) { + this.customizer = customizer; + } + + @Override + public ResponseActions expectRequest(ExpectedCount count, + RequestMatcher requestMatcher) { + return getDelegate().expectRequest(count, requestMatcher); + } + + @Override + public ClientHttpResponse validateRequest(ClientHttpRequest request) + throws IOException { + return getDelegate().validateRequest(request); + } + + @Override + public void verify() { + getDelegate().verify(); + } + + @Override + public void reset() { + Map expectationManagers = this.customizer + .getExpectationManagers(); + if (expectationManagers.size() == 1) { + getDelegate().reset(); + } + } + + private RequestExpectationManager getDelegate() { + Map expectationManagers = this.customizer + .getExpectationManagers(); + Assert.state(expectationManagers.size() > 0, + "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(); + } + + } + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/MockRestServiceServerResetTestExecutionListener.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/MockRestServiceServerResetTestExecutionListener.java new file mode 100644 index 0000000000..59ecb2e400 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/MockRestServiceServerResetTestExecutionListener.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.context.ApplicationContext; +import org.springframework.test.context.TestContext; +import org.springframework.test.context.TestExecutionListener; +import org.springframework.test.context.support.AbstractTestExecutionListener; +import org.springframework.test.web.client.MockRestServiceServer; + +/** + * {@link TestExecutionListener} to reset {@link MockRestServiceServer} beans. + * + * @author Phillip Webb + */ +class MockRestServiceServerResetTestExecutionListener + extends AbstractTestExecutionListener { + + @Override + public void afterTestMethod(TestContext testContext) throws Exception { + ApplicationContext applicationContext = testContext.getApplicationContext(); + String[] names = applicationContext + .getBeanNamesForType(MockRestServiceServer.class, false, false); + for (String name : names) { + applicationContext.getBean(name, MockRestServiceServer.class).reset(); + } + } + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/RestClientExcludeFilter.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/RestClientExcludeFilter.java new file mode 100644 index 0000000000..8e7b8dcd6c --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/RestClientExcludeFilter.java @@ -0,0 +1,97 @@ +/* + * Copyright 2012-2016 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 + * + * http://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 java.io.IOException; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import com.fasterxml.jackson.databind.Module; + +import org.springframework.boot.context.TypeExcludeFilter; +import org.springframework.boot.jackson.JsonComponent; +import org.springframework.boot.test.autoconfigure.filter.AnnotationCustomizableTypeExcludeFilter; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; + +/** + * {@link TypeExcludeFilter} for {@link RestClientTest @RestClientTest}. + * + * @author Stephane Nicoll + */ +class RestClientExcludeFilter extends AnnotationCustomizableTypeExcludeFilter { + + private static final Set> DEFAULT_INCLUDES; + + static { + Set> includes = new LinkedHashSet>(); + includes.add(Module.class); + includes.add(JsonComponent.class); + DEFAULT_INCLUDES = Collections.unmodifiableSet(includes); + } + + private final RestClientTest annotation; + + RestClientExcludeFilter(Class testClass) { + this.annotation = AnnotatedElementUtils.getMergedAnnotation(testClass, + RestClientTest.class); + } + + @Override + protected boolean defaultInclude(MetadataReader metadataReader, + MetadataReaderFactory metadataReaderFactory) throws IOException { + if (super.defaultInclude(metadataReader, metadataReaderFactory)) { + return true; + } + for (Class controller : this.annotation.components()) { + if (isTypeOrAnnotated(metadataReader, metadataReaderFactory, controller)) { + return true; + } + } + return false; + } + + @Override + protected boolean hasAnnotation() { + return this.annotation != null; + } + + @Override + protected Filter[] getFilters(FilterType type) { + switch (type) { + case INCLUDE: + return this.annotation.includeFilters(); + case EXCLUDE: + return this.annotation.excludeFilters(); + } + throw new IllegalStateException("Unsupported type " + type); + } + + @Override + protected boolean isUseDefaultFilters() { + return this.annotation.useDefaultFilters(); + } + + @Override + protected Set> getDefaultIncludes() { + return DEFAULT_INCLUDES; + } + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTest.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTest.java new file mode 100644 index 0000000000..f345c18d2c --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTest.java @@ -0,0 +1,116 @@ +/* + * Copyright 2012-2016 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 + * + * http://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 java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration; +import org.springframework.boot.test.autoconfigure.core.AutoConfigureCache; +import org.springframework.boot.test.autoconfigure.filter.TypeExcludeFilters; +import org.springframework.boot.test.context.SpringBootTestContextBootstrapper; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.core.annotation.AliasFor; +import org.springframework.stereotype.Component; +import org.springframework.test.context.BootstrapWith; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestTemplate; + +/** + * Annotation that can be used in combination with {@code @RunWith(SpringRunner.class)} + * for a typical Spring rest client test. Can be used when a test focuses + * only on beans that use {@link RestTemplateBuilder}. + *

+ * Using this annotation will disable full auto-configuration and instead apply only + * configuration relevant to rest client tests (i.e. Jackson or GSON auto-configureation + * and {@code @JsonComponent} beans, but not regular {@link Component @Component} beans). + *

+ * By default, tests annotated with {@code RestClientTest} will also auto-configure a + * {@link MockRestServiceServer}. For more fine-grained control the + * {@link AutoConfigureMockRestServiceServer @AutoConfigureMockRestServiceServer} + * annotation can be used. + *

+ * If you are testing a bean that doesn't use {@link RestTemplateBuilder} but instead + * injects a {@link RestTemplate} directly, you can add + * {@code @AutoConfigureWebClient(registerRestTemplate=true)}. + * + * @author Stephane Nicoll + * @author Phillip Webb + * @since 1.4.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@BootstrapWith(SpringBootTestContextBootstrapper.class) +@OverrideAutoConfiguration(enabled = false) +@TypeExcludeFilters(RestClientExcludeFilter.class) +@AutoConfigureCache +@AutoConfigureWebClient +@AutoConfigureMockRestServiceServer +public @interface RestClientTest { + + /** + * Specifies the components to test. This is an alias of {@link #components()} which + * can be used for brevity if no other attributes are defined. See + * {@link #components()} for details. + * @see #components() + * @return the components to test + */ + @AliasFor("components") + Class[] value() default {}; + + /** + * Specifies the components to test. May be left blank if components will be manually + * imported or created directly. + * @see #value() + * @return the components to test + */ + @AliasFor("value") + Class[] components() default {}; + + /** + * Determines if default filtering should be used with + * {@link SpringBootApplication @SpringBootApplication}. By default only + * {@code @JsonComponent} and {@code Module} beans are included. + * @see #includeFilters() + * @see #excludeFilters() + * @return if default filters should be used + */ + boolean useDefaultFilters() default true; + + /** + * A set of include filters which can be used to add otherwise filtered beans to the + * application context. + * @return include filters to apply + */ + ComponentScan.Filter[] includeFilters() default {}; + + /** + * A set of exclude filters which can be used to filter beans that would otherwise be + * added to the application context. + * @return exclude filters to apply + */ + ComponentScan.Filter[] excludeFilters() default {}; + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/WebClientRestTemplateAutoConfiguration.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/WebClientRestTemplateAutoConfiguration.java new file mode 100644 index 0000000000..e3ef27ad62 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/WebClientRestTemplateAutoConfiguration.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +/** + * Auto-configuration for a web-client {@link RestTemplate}. Used when + * {@link AutoConfigureWebClient#registerRestTemplate()} is {@code true}. + * + * @author Phillip Webb + * @see AutoConfigureMockRestServiceServer + */ +@Configuration +@ConditionalOnProperty(prefix = "spring.test.webclient", name = "register-rest-template") +@AutoConfigureAfter(WebClientAutoConfiguration.class) +class WebClientRestTemplateAutoConfiguration { + + @Bean + public RestTemplate restTemplate(RestTemplateBuilder builder) { + return builder.build(); + } + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/package-info.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/package-info.java new file mode 100644 index 0000000000..8dc75a46d0 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/client/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2016 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 + * + * http://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. + */ + +/** + * Auto-configuration for web clients. + */ +package org.springframework.boot.test.autoconfigure.web.client; diff --git a/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories index d7c4a80ee5..e57708b788 100644 --- a/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories @@ -24,6 +24,10 @@ org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityAutoConfi org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration,\ org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration +# AutoConfigureMockRestServiceServer +org.springframework.boot.test.autoconfigure.web.client.AutoConfigureMockRestServiceServer=\ +org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerAutoConfiguration + # AutoConfigureRestDocs auto-configuration imports org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs=\ org.springframework.boot.test.autoconfigure.restdocs.RestDocsAutoConfiguration @@ -36,6 +40,14 @@ org.springframework.boot.test.autoconfigure.orm.jpa.TestDatabaseAutoConfiguratio org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestEntityManager=\ org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration +# AutoConfigureWebClient auto-configuration imports +org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient=\ +org.springframework.boot.test.autoconfigure.web.client.WebClientRestTemplateAutoConfiguration,\ +org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ +org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\ +org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\ +org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration + # AutoConfigureWebMvc auto-configuration imports org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc=\ org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\ @@ -58,4 +70,5 @@ org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCus org.springframework.test.context.TestExecutionListener=\ org.springframework.boot.test.autoconfigure.AutoConfigureReportTestExecutionListener,\ org.springframework.boot.test.autoconfigure.json.JsonTesterInitializationTestExecutionListener,\ -org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener +org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener,\ +org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestClient.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestClient.java new file mode 100644 index 0000000000..94a08561db --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AnotherExampleRestClient.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.boot.web.client.RestTemplateBuilder; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +/** + * A second example web client used with {@link RestClientTest} tests. + * + * @author Phillip Webb + */ +@Service +public class AnotherExampleRestClient { + + private RestTemplate restTemplate; + + public AnotherExampleRestClient(RestTemplateBuilder builder) { + this.restTemplate = builder.rootUri("http://example.com").build(); + } + + protected RestTemplate getRestTemplate() { + return this.restTemplate; + } + + public String test() { + return this.restTemplate.getForEntity("/test", String.class).getBody(); + } + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerEnabledFalseIntegrationTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerEnabledFalseIntegrationTests.java new file mode 100644 index 0000000000..09d5dd709c --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureMockRestServiceServerEnabledFalseIntegrationTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.web.client.MockServerRestTemplateCustomizer; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Tests for {@link AutoConfigureMockRestServiceServer} with {@code enabled=false}. + * + * @author Phillip Webb + */ +@RunWith(SpringRunner.class) +@RestClientTest +@AutoConfigureMockRestServiceServer(enabled = false) +public class AutoConfigureMockRestServiceServerEnabledFalseIntegrationTests { + + @Autowired + private ApplicationContext applicationContext; + + @Test(expected = NoSuchBeanDefinitionException.class) + public void mockServerRestTemplateCustomizerShouldNotBeRegistered() throws Exception { + this.applicationContext.getBean(MockServerRestTemplateCustomizer.class); + } + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureWebClientWithRestTemplateIntegrationTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureWebClientWithRestTemplateIntegrationTests.java new file mode 100644 index 0000000000..ad51870005 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureWebClientWithRestTemplateIntegrationTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestTemplate; + +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 AutoConfigureWebClient} with {@code registerRestTemplate=true}. + * + * @author Phillip Webb + */ +@RunWith(SpringRunner.class) +@SpringBootTest +@AutoConfigureWebClient(registerRestTemplate = true) +@AutoConfigureMockRestServiceServer +public class AutoConfigureWebClientWithRestTemplateIntegrationTests { + + @Autowired + private RestTemplate restTemplate; + + @Autowired + private MockRestServiceServer server; + + @Test + public void restTemplateTest() throws Exception { + this.server.expect(requestTo("/test")) + .andRespond(withSuccess("hello", MediaType.TEXT_HTML)); + ResponseEntity entity = this.restTemplate.getForEntity("/test", + String.class); + assertThat(entity.getBody()).isEqualTo("hello"); + } + + @Configuration + @EnableAutoConfiguration + static class Config { + + } + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestClient.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestClient.java new file mode 100644 index 0000000000..1132a5afe4 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleRestClient.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.boot.web.client.RestTemplateBuilder; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +/** + * Example web client used with {@link RestClientTest} tests. + * + * @author Phillip Webb + */ +@Service +public class ExampleRestClient { + + private RestTemplate restTemplate; + + public ExampleRestClient(RestTemplateBuilder builder) { + this.restTemplate = builder.rootUri("http://example.com").build(); + } + + protected RestTemplate getRestTemplate() { + return this.restTemplate; + } + + public String test() { + return this.restTemplate.getForEntity("/test", String.class).getBody(); + } + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleWebClientApplication.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleWebClientApplication.java new file mode 100644 index 0000000000..a218af4a00 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/ExampleWebClientApplication.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.boot.autoconfigure.SpringBootApplication; + +/** + * Example {@link SpringBootApplication} used with {@link RestClientTest} tests. + * + * @author Phillip Webb + */ +@SpringBootApplication +public class ExampleWebClientApplication { + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientRestIntegrationTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientRestIntegrationTests.java new file mode 100644 index 0000000000..b1928940c3 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientRestIntegrationTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +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} gets reset after test methods. + * + * @author Phillip Webb + */ +@RunWith(SpringRunner.class) +@RestClientTest(ExampleRestClient.class) +public class RestClientRestIntegrationTests { + + @Autowired + private MockRestServiceServer server; + + @Autowired + private ExampleRestClient client; + + @Test + public void mockServerCall1() throws Exception { + this.server.expect(requestTo("/test")) + .andRespond(withSuccess("1", MediaType.TEXT_HTML)); + assertThat(this.client.test()).isEqualTo("1"); + } + + @Test + public void mockServerCall2() throws Exception { + this.server.expect(requestTo("/test")) + .andRespond(withSuccess("2", MediaType.TEXT_HTML)); + assertThat(this.client.test()).isEqualTo("2"); + } + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestNoComponentIntegrationTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestNoComponentIntegrationTests.java new file mode 100644 index 0000000000..7e954818c7 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestNoComponentIntegrationTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.ApplicationContext; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +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} with no specific client. + * + * @author Phillip Webb + */ +@RunWith(SpringRunner.class) +@RestClientTest +public class RestClientTestNoComponentIntegrationTests { + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + private RestTemplateBuilder restTemplateBuilder; + + @Autowired + private MockRestServiceServer server; + + @Test(expected = NoSuchBeanDefinitionException.class) + public void exampleRestClientIsNotInjected() throws Exception { + this.applicationContext.getBean(ExampleRestClient.class); + } + + @Test + public void manuallyCreateBean() throws Exception { + ExampleRestClient client = new ExampleRestClient(this.restTemplateBuilder); + this.server.expect(requestTo("/test")) + .andRespond(withSuccess("hello", MediaType.TEXT_HTML)); + assertThat(client.test()).isEqualTo("hello"); + } + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestTwoComponentsIntegrationTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestTwoComponentsIntegrationTests.java new file mode 100644 index 0000000000..f387ca784e --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestTwoComponentsIntegrationTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.web.client.MockServerRestTemplateCustomizer; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +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} with two clients. + * + * @author Phillip Webb + */ +@RunWith(SpringRunner.class) +@RestClientTest({ ExampleRestClient.class, AnotherExampleRestClient.class }) +public class RestClientTestTwoComponentsIntegrationTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Autowired + private ExampleRestClient client1; + + @Autowired + private AnotherExampleRestClient client2; + + @Autowired + private MockServerRestTemplateCustomizer customizer; + + @Autowired + private MockRestServiceServer server; + + @Test + public void serverShouldNotWork() throws Exception { + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("Unable to use auto-configured"); + this.server.expect(requestTo("/test")) + .andRespond(withSuccess("hello", MediaType.TEXT_HTML)); + } + + @Test + public void client1RestCallViaCustomizer() throws Exception { + this.customizer.getServer(this.client1.getRestTemplate()) + .expect(requestTo("/test")) + .andRespond(withSuccess("hello", MediaType.TEXT_HTML)); + assertThat(this.client1.test()).isEqualTo("hello"); + } + + @Test + public void client2RestCallViaCustomizer() throws Exception { + this.customizer.getServer(this.client2.getRestTemplate()) + .expect(requestTo("/test")) + .andRespond(withSuccess("there", MediaType.TEXT_HTML)); + assertThat(this.client2.test()).isEqualTo("there"); + } + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithComponentIntegrationTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithComponentIntegrationTests.java new file mode 100644 index 0000000000..b41642a7b0 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientTestWithComponentIntegrationTests.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +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} with a single client. + * + * @author Phillip Webb + */ +@RunWith(SpringRunner.class) +@RestClientTest(ExampleRestClient.class) +public class RestClientTestWithComponentIntegrationTests { + + @Autowired + private MockRestServiceServer server; + + @Autowired + private ExampleRestClient client; + + @Test + public void mockServerCall() throws Exception { + this.server.expect(requestTo("/test")) + .andRespond(withSuccess("hello", MediaType.TEXT_HTML)); + assertThat(this.client.test()).isEqualTo("hello"); + } + +} diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/MockServerRestTemplateCustomizer.java b/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/MockServerRestTemplateCustomizer.java new file mode 100644 index 0000000000..0b5804f6bf --- /dev/null +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/MockServerRestTemplateCustomizer.java @@ -0,0 +1,124 @@ +/* + * Copyright 2012-2016 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 + * + * http://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 org.springframework.beans.BeanUtils; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.boot.web.client.RestTemplateCustomizer; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.test.web.client.RequestExpectationManager; +import org.springframework.test.web.client.SimpleRequestExpectationManager; +import org.springframework.util.Assert; +import org.springframework.web.client.RestTemplate; + +/** + * {@link RestTemplateCustomizer} that can be applied to a {@link RestTemplateBuilder} + * instances to add {@link MockRestServiceServer} support. + *

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

+ * MockServerRestTemplateCustomizer customizer = new MockServerRestTemplateCustomizer();
+ * MyBean bean = new MyBean(new RestTemplateBuilder(customizer));
+ * 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(RestTemplate)} or {@link #getServers()} method must be used to access + * the related server. + * + * @author Phillip Webb + * @since 1.4.0 + * @see #getServer() + * @see #getServer(RestTemplate) + */ +public class MockServerRestTemplateCustomizer implements RestTemplateCustomizer { + + private Map expectationManagers = new ConcurrentHashMap(); + + private Map servers = new ConcurrentHashMap(); + + private final Class expectationManager; + + private boolean detectRootUri = true; + + public MockServerRestTemplateCustomizer() { + this.expectationManager = SimpleRequestExpectationManager.class; + } + + public MockServerRestTemplateCustomizer( + Class expectationManager) { + Assert.notNull(expectationManager, "ExpectationManager must not be null"); + this.expectationManager = expectationManager; + } + + /** + * Set if root URIs from {@link RootUriRequestExpectationManager} should be detected + * and applied to the {@link MockRestServiceServer}. + * @param detectRootUri if root URIs should be detected + */ + public void setDetectRootUri(boolean detectRootUri) { + this.detectRootUri = detectRootUri; + } + + @Override + public void customize(RestTemplate restTemplate) { + RequestExpectationManager expectationManager = createExpecationManager(); + if (this.detectRootUri) { + expectationManager = RootUriRequestExpectationManager + .forRestTemplate(restTemplate, expectationManager); + } + MockRestServiceServer server = MockRestServiceServer.bindTo(restTemplate) + .build(expectationManager); + this.expectationManagers.put(restTemplate, expectationManager); + this.servers.put(restTemplate, server); + } + + protected RequestExpectationManager createExpecationManager() { + return BeanUtils.instantiate(this.expectationManager); + } + + public MockRestServiceServer getServer() { + Assert.state(this.servers.size() > 0, + "Unable to return a single MockRestServiceServer since " + + "MockServerRestTemplateCustomizer has not been bound to " + + "a RestTemplate"); + Assert.state(this.servers.size() == 1, + "Unable to return a single MockRestServiceServer since " + + "MockServerRestTemplateCustomizer has been bound to " + + "more than one RestTemplate"); + return this.servers.values().iterator().next(); + } + + public Map getExpectationManagers() { + return this.expectationManagers; + } + + public MockRestServiceServer getServer(RestTemplate restTemplate) { + return this.servers.get(restTemplate); + } + + public Map getServers() { + return Collections.unmodifiableMap(this.servers); + } + +} diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/RootUriRequestExpectationManager.java b/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/RootUriRequestExpectationManager.java new file mode 100644 index 0000000000..80a74c8109 --- /dev/null +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/RootUriRequestExpectationManager.java @@ -0,0 +1,191 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.net.URISyntaxException; + +import org.springframework.boot.web.client.RootUriTemplateHandler; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.support.HttpRequestWrapper; +import org.springframework.test.web.client.ExpectedCount; +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.RequestMatcher; +import org.springframework.test.web.client.ResponseActions; +import org.springframework.test.web.client.SimpleRequestExpectationManager; +import org.springframework.util.Assert; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriTemplateHandler; + +/** + * {@link RequestExpectationManager} that strips any the specified root URI from the + * request before verification. Can be used to simply test declarations when all REST + * calls start the same way. For example:

+ * RestTemplate restTemplate = new RestTemplateBuilder().rootUri("http://example.com").build();
+ * MockRestServiceServer server = RootUriRequestExpectationManager.bindTo(restTemplate);
+ * server.expect(requestTo("/hello")).andRespond(withSuccess());
+ * restTemplate.getForEntity("/hello", String.class);
+ * 
+ * + * @author Phillip Webb + * @since 1.4.0 + * @see RootUriTemplateHandler + * @see #bindTo(RestTemplate) + * @see #forRestTemplate(RestTemplate, RequestExpectationManager) + */ +public class RootUriRequestExpectationManager implements RequestExpectationManager { + + private final String rootUri; + + private final RequestExpectationManager expectationManager; + + public RootUriRequestExpectationManager(String rootUri, + RequestExpectationManager expectationManager) { + Assert.notNull(rootUri, "RootUri must not be null"); + Assert.notNull(expectationManager, "ExpectationManager must not be null"); + this.rootUri = rootUri; + this.expectationManager = expectationManager; + } + + @Override + public ResponseActions expectRequest(ExpectedCount count, + RequestMatcher requestMatcher) { + return this.expectationManager.expectRequest(count, requestMatcher); + } + + @Override + public ClientHttpResponse validateRequest(ClientHttpRequest request) + throws IOException { + String uri = request.getURI().toString(); + if (uri.startsWith(this.rootUri)) { + uri = uri.substring(this.rootUri.length()); + request = new ReplaceUriClientHttpRequest(uri, request); + } + try { + return this.expectationManager.validateRequest(request); + } + catch (AssertionError ex) { + String message = ex.getMessage(); + String prefix = "Request URI expected: requestCaptor; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + this.manager = new RootUriRequestExpectationManager(this.uri, this.delegate); + } + + @Test + public void createWhenRootUriIsNullShouldThrowException() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("RootUri must not be null"); + new RootUriRequestExpectationManager(null, this.delegate); + } + + @Test + public void createWhenExpectationManagerIsNullShouldThrowException() + throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("ExpectationManager must not be null"); + new RootUriRequestExpectationManager(this.uri, null); + } + + @Test + public void expectRequestShouldDelegateToExpecationManager() throws Exception { + ExpectedCount count = mock(ExpectedCount.class); + RequestMatcher requestMatcher = mock(RequestMatcher.class); + this.manager.expectRequest(count, requestMatcher); + verify(this.delegate).expectRequest(count, requestMatcher); + } + + @Test + public void validateRequestWhenUriDoesNotStartWithRootUriShouldDelegateToExpectationManager() + throws Exception { + ClientHttpRequest request = mock(ClientHttpRequest.class); + given(request.getURI()).willReturn(new URI("http://spring.io/test")); + this.manager.validateRequest(request); + verify(this.delegate).validateRequest(request); + } + + @Test + public void validateRequestWhenUriStartsWithRootUriShouldReplaceUri() + throws Exception { + ClientHttpRequest request = mock(ClientHttpRequest.class); + given(request.getURI()).willReturn(new URI(this.uri + "/hello")); + this.manager.validateRequest(request); + verify(this.delegate).validateRequest(this.requestCaptor.capture()); + HttpRequestWrapper actual = (HttpRequestWrapper) this.requestCaptor.getValue(); + assertThat(actual.getRequest()).isSameAs(request); + assertThat(actual.getURI()).isEqualTo(new URI("/hello")); + } + + @Test + public void validateRequestWhenRequestUriAssertionIsThrownShouldReplaceUriInMessage() + throws Exception { + ClientHttpRequest request = mock(ClientHttpRequest.class); + given(request.getURI()).willReturn(new URI(this.uri + "/hello")); + given(this.delegate.validateRequest((ClientHttpRequest) any())) + .willThrow(new AssertionError( + "Request URI expected: was:")); + this.thrown.expect(AssertionError.class); + this.thrown.expectMessage("Request URI expected:"); + this.manager.validateRequest(request); + } + + @Test + public void resetRequestShouldDelegateToExpecationManager() throws Exception { + this.manager.reset(); + verify(this.delegate).reset(); + } + + @Test + public void bindToShouldReturnMockResetServiceServer() throws Exception { + RestTemplate restTemplate = new RestTemplateBuilder().build(); + MockRestServiceServer bound = RootUriRequestExpectationManager + .bindTo(restTemplate); + assertThat(bound).isNotNull(); + } + + @Test + public void bindToWithExpectationManagerShouldReturnMockResetServiceServer() + throws Exception { + RestTemplate restTemplate = new RestTemplateBuilder().build(); + MockRestServiceServer bound = RootUriRequestExpectationManager + .bindTo(restTemplate, this.delegate); + assertThat(bound).isNotNull(); + } + + @Test + public void forRestTemplateWhenUsingRootUriTemplateHandlerShouldReturnRootUriRequestExpectationManager() + throws Exception { + RestTemplate restTemplate = new RestTemplateBuilder().rootUri(this.uri).build(); + RequestExpectationManager actual = RootUriRequestExpectationManager + .forRestTemplate(restTemplate, this.delegate); + assertThat(actual).isInstanceOf(RootUriRequestExpectationManager.class); + assertThat(actual).extracting("rootUri").containsExactly(this.uri); + } + + @Test + public void forRestTemplateWhenNotUsingRootUriTemplateHandlerShouldReturnOriginalUriRequestExpectationManager() + throws Exception { + RestTemplate restTemplate = new RestTemplateBuilder().build(); + RequestExpectationManager actual = RootUriRequestExpectationManager + .forRestTemplate(restTemplate, this.delegate); + assertThat(actual).isSameAs(this.delegate); + } + + @Test + public void boundRestTemplateShouldPrefixRootUri() { + RestTemplate restTemplate = new RestTemplateBuilder() + .rootUri("http://example.com").build(); + MockRestServiceServer server = RootUriRequestExpectationManager + .bindTo(restTemplate); + server.expect(requestTo("/hello")).andRespond(withSuccess()); + restTemplate.getForEntity("/hello", String.class); + } + + @Test + public void boundRestTemplateWhenUrlIncludesDomainShouldNotPrefixRootUri() { + RestTemplate restTemplate = new RestTemplateBuilder() + .rootUri("http://example.com").build(); + MockRestServiceServer server = RootUriRequestExpectationManager + .bindTo(restTemplate); + server.expect(requestTo("/hello")).andRespond(withSuccess()); + this.thrown.expect(AssertionError.class); + this.thrown.expectMessage( + "expected: but was:"); + restTemplate.getForEntity("http://spring.io/hello", String.class); + } + +}