Create spring-boot-web-server-test module

This commit is contained in:
Andy Wilkinson
2025-05-02 14:03:24 +01:00
committed by Phillip Webb
parent 96bee8e034
commit 4f579905ef
203 changed files with 409 additions and 353 deletions

View File

@@ -29,7 +29,6 @@ import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.boot.web.context.reactive.ReactiveWebApplicationContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
@@ -60,11 +59,7 @@ import org.springframework.web.context.WebApplicationContext;
* <li>Provides support for different {@link #webEnvironment() webEnvironment} modes,
* including the ability to start a fully running web server listening on a
* {@link WebEnvironment#DEFINED_PORT defined} or {@link WebEnvironment#RANDOM_PORT
* random} port.</li>
* <li>Registers a {@link org.springframework.boot.test.web.client.TestRestTemplate
* TestRestTemplate} and/or
* {@link org.springframework.test.web.reactive.server.WebTestClient WebTestClient} bean
* for use in web tests that are using a fully running web server.</li>
* random} port when {@code spring-boot-web-server-test} is on the classpath.</li>
* </ul>
*
* @author Phillip Webb
@@ -148,14 +143,16 @@ public @interface SpringBootTest {
/**
* Creates a web application context (reactive or servlet based) and sets a
* {@code server.port=0} {@link Environment} property (which usually triggers
* listening on a random port). Often used in conjunction with a
* {@link LocalServerPort @LocalServerPort} injected field on the test.
* listening on a random port). Requires a dependency on
* {@code spring-boot-web-server-test}. Often used in conjunction with a
* {@code @LocalServerPort} injected field on the test.
*/
RANDOM_PORT(true),
/**
* Creates a web application context (reactive or servlet based) without defining
* any {@code server.port=0} {@link Environment} property.
* any {@code server.port=0} {@link Environment} property. Requires a dependency
* on {@code spring-boot-web-server-test}.
*/
DEFINED_PORT(true),

View File

@@ -1,108 +0,0 @@
/*
* 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;
import java.util.Objects;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.util.ClassUtils;
/**
* {@link EnvironmentPostProcessor} implementation to start the management context on a
* random port if the main server's port is 0 and the management context is expected on a
* different port.
*
* @author Madhura Bhave
* @author Andy Wilkinson
*/
class SpringBootTestRandomPortEnvironmentPostProcessor implements EnvironmentPostProcessor {
private static final String MANAGEMENT_PORT_PROPERTY = "management.server.port";
private static final String SERVER_PORT_PROPERTY = "server.port";
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
MapPropertySource source = (MapPropertySource) environment.getPropertySources()
.get(TestPropertySourceUtils.INLINED_PROPERTIES_PROPERTY_SOURCE_NAME);
if (source == null || isTestServerPortFixed(source, environment) || isTestManagementPortConfigured(source)) {
return;
}
Integer managementPort = getPropertyAsInteger(environment, MANAGEMENT_PORT_PROPERTY, null);
if (managementPort == null || managementPort.equals(-1) || managementPort.equals(0)) {
return;
}
Integer serverPort = getPropertyAsInteger(environment, SERVER_PORT_PROPERTY, 8080);
if (!managementPort.equals(serverPort)) {
source.getSource().put(MANAGEMENT_PORT_PROPERTY, "0");
}
else {
source.getSource().put(MANAGEMENT_PORT_PROPERTY, "");
}
}
private boolean isTestServerPortFixed(MapPropertySource source, ConfigurableEnvironment environment) {
return !Integer.valueOf(0).equals(getPropertyAsInteger(source, SERVER_PORT_PROPERTY, environment));
}
private boolean isTestManagementPortConfigured(PropertySource<?> source) {
return source.getProperty(MANAGEMENT_PORT_PROPERTY) != null;
}
private Integer getPropertyAsInteger(ConfigurableEnvironment environment, String property, Integer defaultValue) {
return environment.getPropertySources()
.stream()
.filter((source) -> !source.getName()
.equals(TestPropertySourceUtils.INLINED_PROPERTIES_PROPERTY_SOURCE_NAME))
.map((source) -> getPropertyAsInteger(source, property, environment))
.filter(Objects::nonNull)
.findFirst()
.orElse(defaultValue);
}
private Integer getPropertyAsInteger(PropertySource<?> source, String property,
ConfigurableEnvironment environment) {
Object value = source.getProperty(property);
if (value == null) {
return null;
}
if (ClassUtils.isAssignableValue(Integer.class, value)) {
return (Integer) value;
}
try {
return environment.getConversionService().convert(value, Integer.class);
}
catch (ConversionFailedException ex) {
if (value instanceof String string) {
return getResolvedValueIfPossible(environment, string);
}
throw ex;
}
}
private Integer getResolvedValueIfPossible(ConfigurableEnvironment environment, String value) {
String resolvedValue = environment.resolveRequiredPlaceholders(value);
return environment.getConversionService().convert(resolvedValue, Integer.class);
}
}

View File

@@ -1,88 +0,0 @@
/*
* Copyright 2012-2025 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 org.springframework.boot.web.client.RootUriTemplateHandler;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriTemplateHandler;
/**
* {@link UriTemplateHandler} will automatically prefix relative URIs with
* <code>localhost:$&#123;local.server.port&#125;</code>.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Eddú Meléndez
* @author Madhura Bhave
* @since 1.4.0
*/
public class LocalHostUriTemplateHandler extends RootUriTemplateHandler {
private static final String PREFIX = "server.servlet.";
private final Environment environment;
private final String scheme;
/**
* Create a new {@code LocalHostUriTemplateHandler} that will generate {@code http}
* URIs using the given {@code environment} to determine the context path and port.
* @param environment the environment used to determine the port
*/
public LocalHostUriTemplateHandler(Environment environment) {
this(environment, "http");
}
/**
* Create a new {@code LocalHostUriTemplateHandler} that will generate URIs with the
* given {@code scheme} and use the given {@code environment} to determine the
* context-path and port.
* @param environment the environment used to determine the port
* @param scheme the scheme of the root uri
* @since 1.4.1
*/
public LocalHostUriTemplateHandler(Environment environment, String scheme) {
this(environment, scheme, new DefaultUriBuilderFactory());
}
/**
* Create a new {@code LocalHostUriTemplateHandler} that will generate URIs with the
* given {@code scheme}, use the given {@code environment} to determine the
* context-path and port and delegate to the given template {@code handler}.
* @param environment the environment used to determine the port
* @param scheme the scheme of the root uri
* @param handler the delegate handler
* @since 2.0.3
*/
public LocalHostUriTemplateHandler(Environment environment, String scheme, UriTemplateHandler handler) {
super(handler);
Assert.notNull(environment, "'environment' must not be null");
Assert.notNull(scheme, "'scheme' must not be null");
this.environment = environment;
this.scheme = scheme;
}
@Override
public String getRootUri() {
String port = this.environment.getProperty("local.server.port", "8080");
String contextPath = this.environment.getProperty(PREFIX + "context-path", "");
return this.scheme + "://localhost:" + port + contextPath;
}
}

View File

@@ -1,187 +0,0 @@
/*
* Copyright 2012-2025 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 org.springframework.aot.AotDetector;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate.HttpClientOption;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.server.AbstractConfigurableWebServerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContextAnnotationUtils;
/**
* {@link ContextCustomizer} for {@link TestRestTemplate}.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
class TestRestTemplateContextCustomizer implements ContextCustomizer {
@Override
public void customizeContext(ConfigurableApplicationContext context,
MergedContextConfiguration mergedContextConfiguration) {
if (AotDetector.useGeneratedArtifacts()) {
return;
}
SpringBootTest springBootTest = TestContextAnnotationUtils
.findMergedAnnotation(mergedContextConfiguration.getTestClass(), SpringBootTest.class);
if (springBootTest.webEnvironment().isEmbedded()) {
registerTestRestTemplate(context);
}
}
private void registerTestRestTemplate(ConfigurableApplicationContext context) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
if (beanFactory instanceof BeanDefinitionRegistry registry) {
registerTestRestTemplate(registry);
}
}
private void registerTestRestTemplate(BeanDefinitionRegistry registry) {
RootBeanDefinition definition = new RootBeanDefinition(TestRestTemplateRegistrar.class);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(TestRestTemplateRegistrar.class.getName(), definition);
}
@Override
public boolean equals(Object obj) {
return (obj != null) && (obj.getClass() == getClass());
}
@Override
public int hashCode() {
return getClass().hashCode();
}
/**
* {@link BeanDefinitionRegistryPostProcessor} that runs after the
* {@link ConfigurationClassPostProcessor} and add a {@link TestRestTemplateFactory}
* bean definition when a {@link TestRestTemplate} hasn't already been registered.
*/
static class TestRestTemplateRegistrar implements BeanDefinitionRegistryPostProcessor, Ordered, BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (AotDetector.useGeneratedArtifacts()) {
return;
}
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory) this.beanFactory,
TestRestTemplate.class, false, false).length == 0) {
registry.registerBeanDefinition(TestRestTemplate.class.getName(),
new RootBeanDefinition(TestRestTemplateFactory.class));
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
/**
* {@link FactoryBean} used to create and configure a {@link TestRestTemplate}.
*/
public static class TestRestTemplateFactory implements FactoryBean<TestRestTemplate>, ApplicationContextAware {
private static final HttpClientOption[] DEFAULT_OPTIONS = {};
private static final HttpClientOption[] SSL_OPTIONS = { HttpClientOption.SSL };
private TestRestTemplate template;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
RestTemplateBuilder builder = getRestTemplateBuilder(applicationContext);
boolean sslEnabled = isSslEnabled(applicationContext);
TestRestTemplate template = new TestRestTemplate(builder, null, null,
sslEnabled ? SSL_OPTIONS : DEFAULT_OPTIONS);
LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler(applicationContext.getEnvironment(),
sslEnabled ? "https" : "http");
template.setUriTemplateHandler(handler);
this.template = template;
}
private boolean isSslEnabled(ApplicationContext context) {
try {
AbstractConfigurableWebServerFactory webServerFactory = context
.getBean(AbstractConfigurableWebServerFactory.class);
return webServerFactory.getSsl() != null && webServerFactory.getSsl().isEnabled();
}
catch (NoSuchBeanDefinitionException ex) {
return false;
}
}
private RestTemplateBuilder getRestTemplateBuilder(ApplicationContext applicationContext) {
try {
return applicationContext.getBean(RestTemplateBuilder.class);
}
catch (NoSuchBeanDefinitionException ex) {
return new RestTemplateBuilder();
}
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public Class<?> getObjectType() {
return TestRestTemplate.class;
}
@Override
public TestRestTemplate getObject() throws Exception {
return this.template;
}
}
}

View File

@@ -1,43 +0,0 @@
/*
* Copyright 2012-2020 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.List;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.TestContextAnnotationUtils;
/**
* {@link ContextCustomizerFactory} for {@link TestRestTemplate}.
*
* @author Andy Wilkinson
* @see TestRestTemplateContextCustomizer
*/
class TestRestTemplateContextCustomizerFactory implements ContextCustomizerFactory {
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
SpringBootTest springBootTest = TestContextAnnotationUtils.findMergedAnnotation(testClass,
SpringBootTest.class);
return (springBootTest != null) ? new TestRestTemplateContextCustomizer() : null;
}
}

View File

@@ -1,53 +0,0 @@
/*
* Copyright 2012-2025 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.htmlunit;
import java.io.IOException;
import org.htmlunit.FailingHttpStatusCodeException;
import org.htmlunit.Page;
import org.htmlunit.WebClient;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
/**
* {@link WebClient} will automatically prefix relative URLs with
* <code>localhost:$&#123;local.server.port&#125;</code>.
*
* @author Phillip Webb
* @since 1.4.0
*/
public class LocalHostWebClient extends WebClient {
private final Environment environment;
public LocalHostWebClient(Environment environment) {
Assert.notNull(environment, "'environment' must not be null");
this.environment = environment;
}
@Override
public <P extends Page> P getPage(String url) throws IOException, FailingHttpStatusCodeException {
if (url.startsWith("/")) {
String port = this.environment.getProperty("local.server.port", "8080");
url = "http://localhost:" + port + url;
}
return super.getPage(url);
}
}

View File

@@ -1,20 +0,0 @@
/*
* Copyright 2012-2019 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.
*/
/**
* HtmlUnit support classes.
*/
package org.springframework.boot.test.web.htmlunit;

View File

@@ -1,69 +0,0 @@
/*
* Copyright 2012-2025 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.htmlunit.webdriver;
import org.htmlunit.BrowserVersion;
import org.openqa.selenium.Capabilities;
import org.springframework.core.env.Environment;
import org.springframework.test.web.servlet.htmlunit.webdriver.WebConnectionHtmlUnitDriver;
import org.springframework.util.Assert;
/**
* {@link LocalHostWebConnectionHtmlUnitDriver} will automatically prefix relative URLs
* with <code>localhost:$&#123;local.server.port&#125;</code>.
*
* @author Phillip Webb
* @since 1.4.0
*/
public class LocalHostWebConnectionHtmlUnitDriver extends WebConnectionHtmlUnitDriver {
private final Environment environment;
public LocalHostWebConnectionHtmlUnitDriver(Environment environment) {
Assert.notNull(environment, "'environment' must not be null");
this.environment = environment;
}
public LocalHostWebConnectionHtmlUnitDriver(Environment environment, boolean enableJavascript) {
super(enableJavascript);
Assert.notNull(environment, "'environment' must not be null");
this.environment = environment;
}
public LocalHostWebConnectionHtmlUnitDriver(Environment environment, BrowserVersion browserVersion) {
super(browserVersion);
Assert.notNull(environment, "'environment' must not be null");
this.environment = environment;
}
public LocalHostWebConnectionHtmlUnitDriver(Environment environment, Capabilities capabilities) {
super(capabilities);
Assert.notNull(environment, "'environment' must not be null");
this.environment = environment;
}
@Override
public void get(String url) {
if (url.startsWith("/")) {
String port = this.environment.getProperty("local.server.port", "8080");
url = "http://localhost:" + port + url;
}
super.get(url);
}
}

View File

@@ -1,20 +0,0 @@
/*
* Copyright 2012-2019 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.
*/
/**
* Selenium support classes.
*/
package org.springframework.boot.test.web.htmlunit.webdriver;

View File

@@ -1,20 +0,0 @@
/*
* Copyright 2012-2019 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.
*/
/**
* Web test utilities and support classes.
*/
package org.springframework.boot.test.web;

View File

@@ -1,38 +0,0 @@
/*
* Copyright 2012-2019 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.reactive.server;
import org.springframework.test.web.reactive.server.WebTestClient.Builder;
/**
* A customizer for a {@link Builder}. Any {@code WebTestClientBuilderCustomizer} beans
* found in the application context will be {@link #customize called} to customize the
* auto-configured {@link Builder}.
*
* @author Andy Wilkinson
* @since 2.2.0
*/
@FunctionalInterface
public interface WebTestClientBuilderCustomizer {
/**
* Customize the given {@code builder}.
* @param builder the builder
*/
void customize(Builder builder);
}

View File

@@ -1,246 +0,0 @@
/*
* Copyright 2012-2025 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.reactive.server;
import java.util.Collection;
import org.springframework.aot.AotDetector;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.boot.web.server.reactive.AbstractReactiveWebServerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContextAnnotationUtils;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
/**
* {@link ContextCustomizer} for {@link WebTestClient}.
*
* @author Stephane Nicoll
*/
class WebTestClientContextCustomizer implements ContextCustomizer {
@Override
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
if (AotDetector.useGeneratedArtifacts()) {
return;
}
SpringBootTest springBootTest = TestContextAnnotationUtils.findMergedAnnotation(mergedConfig.getTestClass(),
SpringBootTest.class);
if (springBootTest.webEnvironment().isEmbedded()) {
registerWebTestClient(context);
}
}
private void registerWebTestClient(ConfigurableApplicationContext context) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
if (beanFactory instanceof BeanDefinitionRegistry registry) {
registerWebTestClient(registry);
}
}
private void registerWebTestClient(BeanDefinitionRegistry registry) {
RootBeanDefinition definition = new RootBeanDefinition(WebTestClientRegistrar.class);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(WebTestClientRegistrar.class.getName(), definition);
}
@Override
public boolean equals(Object obj) {
return (obj != null) && (obj.getClass() == getClass());
}
@Override
public int hashCode() {
return getClass().hashCode();
}
/**
* {@link BeanDefinitionRegistryPostProcessor} that runs after the
* {@link ConfigurationClassPostProcessor} and add a {@link WebTestClientFactory} bean
* definition when a {@link WebTestClient} hasn't already been registered.
*/
static class WebTestClientRegistrar implements BeanDefinitionRegistryPostProcessor, Ordered, BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (AotDetector.useGeneratedArtifacts()) {
return;
}
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory) this.beanFactory,
WebTestClient.class, false, false).length == 0) {
registry.registerBeanDefinition(WebTestClient.class.getName(),
new RootBeanDefinition(WebTestClientFactory.class));
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
/**
* {@link FactoryBean} used to create and configure a {@link WebTestClient}.
*/
public static class WebTestClientFactory implements FactoryBean<WebTestClient>, ApplicationContextAware {
private ApplicationContext applicationContext;
private WebTestClient object;
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.context.reactive.ReactiveWebApplicationContext";
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public Class<?> getObjectType() {
return WebTestClient.class;
}
@Override
public WebTestClient getObject() throws Exception {
if (this.object == null) {
this.object = createWebTestClient();
}
return this.object;
}
private WebTestClient createWebTestClient() {
boolean sslEnabled = isSslEnabled(this.applicationContext);
String port = this.applicationContext.getEnvironment().getProperty("local.server.port", "8080");
String baseUrl = getBaseUrl(sslEnabled, port);
WebTestClient.Builder builder = WebTestClient.bindToServer();
customizeWebTestClientBuilder(builder, this.applicationContext);
customizeWebTestClientCodecs(builder, this.applicationContext);
return builder.baseUrl(baseUrl).build();
}
private String getBaseUrl(boolean sslEnabled, String port) {
String basePath = deduceBasePath();
String pathSegment = (StringUtils.hasText(basePath)) ? basePath : "";
return (sslEnabled ? "https" : "http") + "://localhost:" + port + pathSegment;
}
private String deduceBasePath() {
WebApplicationType webApplicationType = deduceFromApplicationContext(this.applicationContext.getClass());
if (webApplicationType == WebApplicationType.REACTIVE) {
return this.applicationContext.getEnvironment().getProperty("spring.webflux.base-path");
}
else if (webApplicationType == WebApplicationType.SERVLET) {
return ((WebApplicationContext) this.applicationContext).getServletContext().getContextPath();
}
return null;
}
static WebApplicationType deduceFromApplicationContext(Class<?> applicationContextClass) {
if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
return WebApplicationType.SERVLET;
}
if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
return WebApplicationType.REACTIVE;
}
return WebApplicationType.NONE;
}
private static boolean isAssignable(String target, Class<?> type) {
try {
return ClassUtils.resolveClassName(target, null).isAssignableFrom(type);
}
catch (Throwable ex) {
return false;
}
}
private boolean isSslEnabled(ApplicationContext context) {
try {
AbstractReactiveWebServerFactory webServerFactory = context
.getBean(AbstractReactiveWebServerFactory.class);
return webServerFactory.getSsl() != null && webServerFactory.getSsl().isEnabled();
}
catch (NoSuchBeanDefinitionException ex) {
return false;
}
}
private void customizeWebTestClientBuilder(WebTestClient.Builder clientBuilder, ApplicationContext context) {
for (WebTestClientBuilderCustomizer customizer : context
.getBeansOfType(WebTestClientBuilderCustomizer.class)
.values()) {
customizer.customize(clientBuilder);
}
}
private void customizeWebTestClientCodecs(WebTestClient.Builder clientBuilder, ApplicationContext context) {
Collection<CodecCustomizer> codecCustomizers = context.getBeansOfType(CodecCustomizer.class).values();
if (!CollectionUtils.isEmpty(codecCustomizers)) {
clientBuilder.exchangeStrategies(ExchangeStrategies.builder()
.codecs((codecs) -> codecCustomizers
.forEach((codecCustomizer) -> codecCustomizer.customize(codecs)))
.build());
}
}
}
}

View File

@@ -1,52 +0,0 @@
/*
* 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.reactive.server;
import java.util.List;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.TestContextAnnotationUtils;
import org.springframework.util.ClassUtils;
/**
* {@link ContextCustomizerFactory} for {@code WebTestClient}.
*
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Anugrah Singhal
*/
class WebTestClientContextCustomizerFactory implements ContextCustomizerFactory {
private static final boolean webClientPresent;
static {
ClassLoader loader = WebTestClientContextCustomizerFactory.class.getClassLoader();
webClientPresent = ClassUtils.isPresent("org.springframework.web.reactive.function.client.WebClient", loader);
}
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
SpringBootTest springBootTest = TestContextAnnotationUtils.findMergedAnnotation(testClass,
SpringBootTest.class);
return (springBootTest != null && webClientPresent) ? new WebTestClientContextCustomizer() : null;
}
}

View File

@@ -1,21 +0,0 @@
/*
* Copyright 2012-2019 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.
*/
/**
* Spring Boot support for testing Spring WebFlux server endpoints via
* {@link org.springframework.test.web.reactive.server.WebTestClient}.
*/
package org.springframework.boot.test.web.reactive.server;

View File

@@ -1,39 +0,0 @@
/*
* Copyright 2012-2024 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.reactor.netty;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.http.client.ReactorResourceFactory;
/**
* {@link BeanPostProcessor} to disable the use of global resources in
* {@link ReactorResourceFactory} preventing test cleanup issues.
*
* @author Phillip Webb
*/
class DisableReactorResourceFactoryGlobalResourcesBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof ReactorResourceFactory reactorResourceFactory) {
reactorResourceFactory.setUseGlobalResources(false);
}
return bean;
}
}

View File

@@ -1,74 +0,0 @@
/*
* Copyright 2012-2024 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.reactor.netty;
import java.util.List;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.http.client.ReactorResourceFactory;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.util.ClassUtils;
/**
* {@link ContextCustomizerFactory} to disable the use of global resources in
* {@link ReactorResourceFactory} preventing test cleanup issues.
*
* @author Phillip Webb
*/
class DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactory implements ContextCustomizerFactory {
String REACTOR_RESOURCE_FACTORY_CLASS = "org.springframework.http.client.ReactorResourceFactory";
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
if (ClassUtils.isPresent(this.REACTOR_RESOURCE_FACTORY_CLASS, testClass.getClassLoader())) {
return new DisableReactorResourceFactoryGlobalResourcesContextCustomizerCustomizer();
}
return null;
}
static final class DisableReactorResourceFactoryGlobalResourcesContextCustomizerCustomizer
implements ContextCustomizer {
private DisableReactorResourceFactoryGlobalResourcesContextCustomizerCustomizer() {
}
@Override
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
context.getBeanFactory()
.registerSingleton(DisableReactorResourceFactoryGlobalResourcesBeanPostProcessor.class.getName(),
new DisableReactorResourceFactoryGlobalResourcesBeanPostProcessor());
}
@Override
public boolean equals(Object obj) {
return (obj instanceof DisableReactorResourceFactoryGlobalResourcesContextCustomizerCustomizer);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}
}

View File

@@ -1,20 +0,0 @@
/*
* Copyright 2012-2024 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.
*/
/**
* Spring Boot support for testing Reactor Netty.
*/
package org.springframework.boot.test.web.reactor.netty;

View File

@@ -1,41 +0,0 @@
/*
* Copyright 2012-2022 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.server;
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.beans.factory.annotation.Value;
/**
* Annotation at the field or method/constructor parameter level that injects the HTTP
* management port that was allocated at runtime. Provides a convenient alternative for
* <code>&#064;Value(&quot;${local.management.port}&quot;)</code>.
*
* @author Stephane Nicoll
* @since 2.7.0
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Value("${local.management.port}")
public @interface LocalManagementPort {
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright 2012-2022 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.server;
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.beans.factory.annotation.Value;
/**
* Annotation at the field or method/constructor parameter level that injects the HTTP
* server port that was allocated at runtime. Provides a convenient alternative for
* <code>&#064;Value(&quot;${local.server.port}&quot;)</code>.
*
* @author Anand Shah
* @author Stephane Nicoll
* @since 2.7.0
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Value("${local.server.port}")
public @interface LocalServerPort {
}

View File

@@ -1,20 +0,0 @@
/*
* Copyright 2012-2022 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.
*/
/**
* Web server test utilities and support classes.
*/
package org.springframework.boot.test.web.server;

View File

@@ -1,279 +0,0 @@
/*
* Copyright 2012-2025 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 org.springframework.core.ParameterizedTypeReference
import org.springframework.http.HttpEntity
import org.springframework.http.HttpMethod
import org.springframework.http.RequestEntity
import org.springframework.http.ResponseEntity
import org.springframework.web.client.RestClientException
import java.net.URI
/**
* Extension for [TestRestTemplate.getForObject] providing a `getForObject<Foo>(...)`
* variant leveraging Kotlin reified type parameters. Like the original Java method, this
* extension is subject to type erasure. Use [exchange] if you need to retain actual
* generic type arguments.
*
* @author Sebastien Deleuze
* @since 2.0.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> TestRestTemplate.getForObject(url: String, vararg uriVariables: Any): T? =
getForObject(url, T::class.java, *uriVariables)
/**
* Extension for [TestRestTemplate.getForObject] providing a `getForObject<Foo>(...)`
* variant leveraging Kotlin reified type parameters. Like the original Java method, this
* extension is subject to type erasure. Use [exchange] if you need to retain actual
* generic type arguments.
*
* @author Sebastien Deleuze
* @since 2.0.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> TestRestTemplate.getForObject(url: String, uriVariables: Map<String, Any?>): T? =
getForObject(url, T::class.java, uriVariables)
/**
* Extension for [TestRestTemplate.getForObject] providing a `getForObject<Foo>(...)`
* variant leveraging Kotlin reified type parameters. Like the original Java method, this
* extension is subject to type erasure. Use [exchange] if you need to retain actual
* generic type arguments.
*
* @author Sebastien Deleuze
* @since 2.0.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> TestRestTemplate.getForObject(url: URI): T? =
getForObject(url, T::class.java)
/**
* Extension for [TestRestTemplate.getForEntity] providing a `getForEntity<Foo>(...)`
* variant leveraging Kotlin reified type parameters. Like the original Java method, this
* extension is subject to type erasure. Use [exchange] if you need to retain actual
* generic type arguments.
*
* @author Sebastien Deleuze
* @since 2.0.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> TestRestTemplate.getForEntity(url: URI): ResponseEntity<T> =
getForEntity(url, T::class.java)
/**
* Extension for [TestRestTemplate.getForEntity] providing a `getForEntity<Foo>(...)`
* variant leveraging Kotlin reified type parameters. Like the original Java method, this
* extension is subject to type erasure. Use [exchange] if you need to retain actual
* generic type arguments.
*
* @author Sebastien Deleuze
* @since 2.0.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> TestRestTemplate.getForEntity(url: String, vararg uriVariables: Any): ResponseEntity<T> =
getForEntity(url, T::class.java, *uriVariables)
/**
* Extension for [TestRestTemplate.getForEntity] providing a `getForEntity<Foo>(...)`
* variant leveraging Kotlin reified type parameters. Like the original Java method, this
* extension is subject to type erasure. Use [exchange] if you need to retain actual
* generic type arguments.
*
* @author Sebastien Deleuze
* @since 2.0.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> TestRestTemplate.getForEntity(url: String,
uriVariables: Map<String, *>): ResponseEntity<T> =
getForEntity(url, T::class.java, uriVariables)
/**
* Extension for [TestRestTemplate.patchForObject] providing a `patchForObject<Foo>(...)`
* variant leveraging Kotlin reified type parameters. Like the original Java method, this
* extension is subject to type erasure. Use [exchange] if you need to retain actual
* generic type arguments.
*
* @author Sebastien Deleuze
* @since 2.0.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> TestRestTemplate.patchForObject(url: String, request: Any? = null,
vararg uriVariables: Any): T? =
patchForObject(url, request, T::class.java, *uriVariables)
/**
* Extension for [TestRestTemplate.patchForObject] providing a `patchForObject<Foo>(...)`
* variant leveraging Kotlin reified type parameters. Like the original Java method, this
* extension is subject to type erasure. Use [exchange] if you need to retain actual
* generic type arguments.
*
* @author Sebastien Deleuze
* @since 2.0.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> TestRestTemplate.patchForObject(url: String, request: Any? = null,
uriVariables: Map<String, *>): T? =
patchForObject(url, request, T::class.java, uriVariables)
/**
* Extension for [TestRestTemplate.patchForObject] providing a `patchForObject<Foo>(...)`
* variant leveraging Kotlin reified type parameters. Like the original Java method, this
* extension is subject to type erasure. Use [exchange] if you need to retain actual
* generic type arguments.
*
* @author Sebastien Deleuze
* @since 2.0.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> TestRestTemplate.patchForObject(url: URI, request: Any? = null): T? =
patchForObject(url, request, T::class.java)
/**
* Extension for [TestRestTemplate.postForObject] providing a `postForObject<Foo>(...)`
* variant leveraging Kotlin reified type parameters. Like the original Java method, this
* extension is subject to type erasure. Use [exchange] if you need to retain actual
* generic type arguments.
*
* @author Sebastien Deleuze
* @since 2.0.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> TestRestTemplate.postForObject(url: String, request: Any? = null,
vararg uriVariables: Any): T? =
postForObject(url, request, T::class.java, *uriVariables)
/**
* Extension for [TestRestTemplate.postForObject] providing a `postForObject<Foo>(...)`
* variant leveraging Kotlin reified type parameters. Like the original Java method, this
* extension is subject to type erasure. Use [exchange] if you need to retain actual
* generic type arguments.
*
* @author Sebastien Deleuze
* @since 2.0.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> TestRestTemplate.postForObject(url: String, request: Any? = null,
uriVariables: Map<String, *>): T? =
postForObject(url, request, T::class.java, uriVariables)
/**
* Extension for [TestRestTemplate.postForObject] providing a `postForObject<Foo>(...)`
* variant leveraging Kotlin reified type parameters. Like the original Java method, this
* extension is subject to type erasure. Use [exchange] if you need to retain actual
* generic type arguments.
*
* @author Sebastien Deleuze
* @since 2.0.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> TestRestTemplate.postForObject(url: URI, request: Any? = null): T? =
postForObject(url, request, T::class.java)
/**
* Extension for [TestRestTemplate.postForEntity] providing a `postForEntity<Foo>(...)`
* variant leveraging Kotlin reified type parameters. Like the original Java method, this
* extension is subject to type erasure. Use [exchange] if you need to retain actual
* generic type arguments.
*
* @author Sebastien Deleuze
* @since 2.0.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> TestRestTemplate.postForEntity(url: String, request: Any? = null,
vararg uriVariables: Any): ResponseEntity<T> =
postForEntity(url, request, T::class.java, *uriVariables)
/**
* Extension for [TestRestTemplate.postForEntity] providing a `postForEntity<Foo>(...)`
* variant leveraging Kotlin reified type parameters. Like the original Java method, this
* extension is subject to type erasure. Use [exchange] if you need to retain actual
* generic type arguments.
*
* @author Sebastien Deleuze
* @since 2.0.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> TestRestTemplate.postForEntity(url: String, request: Any? = null,
uriVariables: Map<String, *>): ResponseEntity<T> =
postForEntity(url, request, T::class.java, uriVariables)
/**
* Extension for [TestRestTemplate.postForEntity] providing a `postForEntity<Foo>(...)`
* variant leveraging Kotlin reified type parameters. Like the original Java method, this
* extension is subject to type erasure. Use [exchange] if you need to retain actual
* generic type arguments.
*
* @author Sebastien Deleuze
* @since 2.0.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> TestRestTemplate.postForEntity(url: URI, request: Any? = null): ResponseEntity<T> =
postForEntity(url, request, T::class.java)
/**
* Extension for [TestRestTemplate.exchange] providing an `exchange<Foo>(...)`
* variant leveraging Kotlin reified type parameters. This extension is not subject to
* type erasure and retains actual generic type arguments.
*
* @author Sebastien Deleuze
* @since 2.0.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> TestRestTemplate.exchange(url: String, method: HttpMethod,
requestEntity: HttpEntity<*>? = null, vararg uriVariables: Any): ResponseEntity<T> =
exchange(url, method, requestEntity, object : ParameterizedTypeReference<T>() {}, *uriVariables)
/**
* Extension for [TestRestTemplate.exchange] providing an `exchange<Foo>(...)`
* variant leveraging Kotlin reified type parameters. This extension is not subject to
* type erasure and retains actual generic type arguments.
*
* @author Sebastien Deleuze
* @since 2.0.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> TestRestTemplate.exchange(url: String, method: HttpMethod,
requestEntity: HttpEntity<*>? = null, uriVariables: Map<String, *>): ResponseEntity<T> =
exchange(url, method, requestEntity, object : ParameterizedTypeReference<T>() {}, uriVariables)
/**
* Extension for [TestRestTemplate.exchange] providing an `exchange<Foo>(...)`
* variant leveraging Kotlin reified type parameters. This extension is not subject to
* type erasure and retains actual generic type arguments.
*
* @author Sebastien Deleuze
* @since 2.0.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> TestRestTemplate.exchange(url: URI, method: HttpMethod,
requestEntity: HttpEntity<*>? = null): ResponseEntity<T> =
exchange(url, method, requestEntity, object : ParameterizedTypeReference<T>() {})
/**
* Extension for [TestRestTemplate.exchange] providing an `exchange<Foo>(...)`
* variant leveraging Kotlin reified type parameters. This extension is not subject to
* type erasure and retains actual generic type arguments.
*
* @author Sebastien Deleuze
* @since 2.0.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> TestRestTemplate.exchange(requestEntity: RequestEntity<*>): ResponseEntity<T> =
exchange(requestEntity, object : ParameterizedTypeReference<T>() {})

View File

@@ -4,20 +4,13 @@ org.springframework.boot.test.context.ImportsContextCustomizerFactory,\
org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizerFactory,\
org.springframework.boot.test.graphql.tester.HttpGraphQlTesterContextCustomizerFactory,\
org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory,\
org.springframework.boot.test.mock.mockito.MockitoContextCustomizerFactory,\
org.springframework.boot.test.web.client.TestRestTemplateContextCustomizerFactory,\
org.springframework.boot.test.web.reactive.server.WebTestClientContextCustomizerFactory,\
org.springframework.boot.test.web.reactor.netty.DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactory
org.springframework.boot.test.mock.mockito.MockitoContextCustomizerFactory
# Test Execution Listeners
org.springframework.test.context.TestExecutionListener=\
org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener,\
org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.test.web.SpringBootTestRandomPortEnvironmentPostProcessor
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.test.context.filter.ExcludeFilterApplicationContextInitializer
org.springframework.boot.test.context.filter.ExcludeFilterApplicationContextInitializer

View File

@@ -1,214 +0,0 @@
/*
* Copyright 2012-2024 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;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.util.PlaceholderResolutionException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link SpringBootTestRandomPortEnvironmentPostProcessor}.
*
* @author Madhura Bhave
* @author Andy Wilkinson
*/
class SpringBootTestRandomPortEnvironmentPostProcessorTests {
private final SpringBootTestRandomPortEnvironmentPostProcessor postProcessor = new SpringBootTestRandomPortEnvironmentPostProcessor();
private MockEnvironment environment;
private MutablePropertySources propertySources;
@BeforeEach
void setup() {
this.environment = new MockEnvironment();
this.propertySources = this.environment.getPropertySources();
}
@Test
void postProcessWhenServerAndManagementPortIsZeroInTestPropertySource() {
addTestPropertySource("0", "0");
this.postProcessor.postProcessEnvironment(this.environment, null);
assertThat(this.environment.getProperty("server.port")).isEqualTo("0");
assertThat(this.environment.getProperty("management.server.port")).isEqualTo("0");
}
@Test
void postProcessWhenServerPortAndManagementPortIsZeroInDifferentPropertySources() {
addTestPropertySource("0", null);
Map<String, Object> source = new HashMap<>();
source.put("management.server.port", "0");
this.propertySources.addLast(new MapPropertySource("other", source));
this.postProcessor.postProcessEnvironment(this.environment, null);
assertThat(this.environment.getProperty("server.port")).isEqualTo("0");
assertThat(this.environment.getProperty("management.server.port")).isEqualTo("0");
}
@Test
void postProcessWhenTestServerAndTestManagementPortAreNonZero() {
addTestPropertySource("8080", "8081");
this.environment.setProperty("server.port", "8080");
this.environment.setProperty("management.server.port", "8081");
this.postProcessor.postProcessEnvironment(this.environment, null);
assertThat(this.environment.getProperty("server.port")).isEqualTo("8080");
assertThat(this.environment.getProperty("management.server.port")).isEqualTo("8081");
}
@Test
void postProcessWhenTestServerPortIsZeroAndTestManagementPortIsNotNull() {
addTestPropertySource("0", "8080");
this.postProcessor.postProcessEnvironment(this.environment, null);
assertThat(this.environment.getProperty("server.port")).isEqualTo("0");
assertThat(this.environment.getProperty("management.server.port")).isEqualTo("8080");
}
@Test
void postProcessWhenTestServerPortIsZeroAndManagementPortIsNull() {
addTestPropertySource("0", null);
this.postProcessor.postProcessEnvironment(this.environment, null);
assertThat(this.environment.getProperty("server.port")).isEqualTo("0");
assertThat(this.environment.getProperty("management.server.port")).isNull();
}
@Test
void postProcessWhenTestServerPortIsZeroAndManagementPortIsNotNullAndSameInProduction() {
addTestPropertySource("0", null);
Map<String, Object> other = new HashMap<>();
other.put("server.port", "8081");
other.put("management.server.port", "8081");
MapPropertySource otherSource = new MapPropertySource("other", other);
this.propertySources.addLast(otherSource);
this.postProcessor.postProcessEnvironment(this.environment, null);
assertThat(this.environment.getProperty("server.port")).isEqualTo("0");
assertThat(this.environment.getProperty("management.server.port")).isEmpty();
}
@Test
void postProcessWhenTestServerPortIsZeroAndManagementPortIsNotNullAndDefaultSameInProduction() {
// mgmt port is 8080 which means it's on the same port as main server since that
// is null in app properties
addTestPropertySource("0", null);
this.propertySources
.addLast(new MapPropertySource("other", Collections.singletonMap("management.server.port", "8080")));
this.postProcessor.postProcessEnvironment(this.environment, null);
assertThat(this.environment.getProperty("server.port")).isEqualTo("0");
assertThat(this.environment.getProperty("management.server.port")).isEmpty();
}
@Test
void postProcessWhenTestServerPortIsZeroAndManagementPortIsNotNullAndDifferentInProduction() {
addTestPropertySource("0", null);
this.propertySources
.addLast(new MapPropertySource("other", Collections.singletonMap("management.server.port", "8081")));
this.postProcessor.postProcessEnvironment(this.environment, null);
assertThat(this.environment.getProperty("server.port")).isEqualTo("0");
assertThat(this.environment.getProperty("management.server.port")).isEqualTo("0");
}
@Test
void postProcessWhenTestServerPortIsZeroAndManagementPortMinusOne() {
addTestPropertySource("0", null);
this.propertySources
.addLast(new MapPropertySource("other", Collections.singletonMap("management.server.port", "-1")));
this.postProcessor.postProcessEnvironment(this.environment, null);
assertThat(this.environment.getProperty("server.port")).isEqualTo("0");
assertThat(this.environment.getProperty("management.server.port")).isEqualTo("-1");
}
@Test
void postProcessWhenTestServerPortIsZeroAndManagementPortIsAnInteger() {
addTestPropertySource("0", null);
this.propertySources
.addLast(new MapPropertySource("other", Collections.singletonMap("management.server.port", 8081)));
this.postProcessor.postProcessEnvironment(this.environment, null);
assertThat(this.environment.getProperty("server.port")).isEqualTo("0");
assertThat(this.environment.getProperty("management.server.port")).isEqualTo("0");
}
@Test
void postProcessWhenManagementServerPortPlaceholderPresentShouldResolvePlaceholder() {
addTestPropertySource("0", null);
MapPropertySource testPropertySource = (MapPropertySource) this.propertySources
.get(TestPropertySourceUtils.INLINED_PROPERTIES_PROPERTY_SOURCE_NAME);
testPropertySource.getSource().put("port", "9090");
this.propertySources
.addLast(new MapPropertySource("other", Collections.singletonMap("management.server.port", "${port}")));
this.postProcessor.postProcessEnvironment(this.environment, null);
assertThat(this.environment.getProperty("server.port")).isEqualTo("0");
assertThat(this.environment.getProperty("management.server.port")).isEqualTo("0");
}
@Test
void postProcessWhenManagementServerPortPlaceholderAbsentShouldFail() {
addTestPropertySource("0", null);
this.propertySources
.addLast(new MapPropertySource("other", Collections.singletonMap("management.server.port", "${port}")));
assertThatExceptionOfType(PlaceholderResolutionException.class)
.isThrownBy(() -> this.postProcessor.postProcessEnvironment(this.environment, null))
.withMessage("Could not resolve placeholder 'port' in value \"${port}\"");
}
@Test
void postProcessWhenServerPortPlaceholderPresentShouldResolvePlaceholder() {
addTestPropertySource("0", null);
MapPropertySource testPropertySource = (MapPropertySource) this.propertySources
.get(TestPropertySourceUtils.INLINED_PROPERTIES_PROPERTY_SOURCE_NAME);
testPropertySource.getSource().put("port", "8080");
Map<String, Object> source = new HashMap<>();
source.put("server.port", "${port}");
source.put("management.server.port", "9090");
this.propertySources.addLast(new MapPropertySource("other", source));
this.postProcessor.postProcessEnvironment(this.environment, null);
assertThat(this.environment.getProperty("server.port")).isEqualTo("0");
assertThat(this.environment.getProperty("management.server.port")).isEqualTo("0");
}
@Test
void postProcessWhenServerPortPlaceholderAbsentShouldFail() {
addTestPropertySource("0", null);
Map<String, Object> source = new HashMap<>();
source.put("server.port", "${port}");
source.put("management.server.port", "9090");
this.propertySources.addLast(new MapPropertySource("other", source));
assertThatExceptionOfType(PlaceholderResolutionException.class)
.isThrownBy(() -> this.postProcessor.postProcessEnvironment(this.environment, null))
.withMessage("Could not resolve placeholder 'port' in value \"${port}\"");
}
private void addTestPropertySource(String serverPort, String managementPort) {
Map<String, Object> source = new HashMap<>();
source.put("server.port", serverPort);
source.put("management.server.port", managementPort);
MapPropertySource inlineTestSource = new MapPropertySource(
TestPropertySourceUtils.INLINED_PROPERTIES_PROPERTY_SOURCE_NAME, source);
this.propertySources.addFirst(inlineTestSource);
}
}

View File

@@ -1,105 +0,0 @@
/*
* Copyright 2012-2025 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.net.URI;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.web.util.UriTemplateHandler;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link LocalHostUriTemplateHandler}.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Eddú Meléndez
*/
class LocalHostUriTemplateHandlerTests {
@Test
void createWhenEnvironmentIsNullShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> new LocalHostUriTemplateHandler(null))
.withMessageContaining("'environment' must not be null");
}
@Test
void createWhenSchemeIsNullShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new LocalHostUriTemplateHandler(new MockEnvironment(), null))
.withMessageContaining("'scheme' must not be null");
}
@Test
void createWhenHandlerIsNullShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new LocalHostUriTemplateHandler(new MockEnvironment(), "http", null))
.withMessageContaining("'handler' must not be null");
}
@Test
void getRootUriShouldUseLocalServerPort() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("local.server.port", "1234");
LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler(environment);
assertThat(handler.getRootUri()).isEqualTo("http://localhost:1234");
}
@Test
void getRootUriWhenLocalServerPortMissingShouldUsePort8080() {
MockEnvironment environment = new MockEnvironment();
LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler(environment);
assertThat(handler.getRootUri()).isEqualTo("http://localhost:8080");
}
@Test
void getRootUriUsesCustomScheme() {
MockEnvironment environment = new MockEnvironment();
LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler(environment, "https");
assertThat(handler.getRootUri()).isEqualTo("https://localhost:8080");
}
@Test
void getRootUriShouldUseContextPath() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("server.servlet.context-path", "/foo");
LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler(environment);
assertThat(handler.getRootUri()).isEqualTo("http://localhost:8080/foo");
}
@Test
void expandShouldUseCustomHandler() {
MockEnvironment environment = new MockEnvironment();
UriTemplateHandler uriTemplateHandler = mock(UriTemplateHandler.class);
Map<String, ?> uriVariables = new HashMap<>();
URI uri = URI.create("https://www.example.com");
given(uriTemplateHandler.expand("https://localhost:8080/", uriVariables)).willReturn(uri);
LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler(environment, "https", uriTemplateHandler);
assertThat(handler.expand("/", uriVariables)).isEqualTo(uri);
then(uriTemplateHandler).should().expand("https://localhost:8080/", uriVariables);
}
}

View File

@@ -1,91 +0,0 @@
/*
* 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 org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer.TestRestTemplateRegistrar;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.test.context.MergedContextConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link TestRestTemplateContextCustomizer}.
*
* @author Andy Wilkinson
*/
class TestRestTemplateContextCustomizerTests {
@Test
void whenContextIsNotABeanDefinitionRegistryTestRestTemplateIsRegistered() {
new ApplicationContextRunner(TestApplicationContext::new)
.withInitializer(this::applyTestRestTemplateContextCustomizer)
.run((context) -> assertThat(context).hasSingleBean(TestRestTemplate.class));
}
@Test
void whenUsingAotGeneratedArtifactsTestRestTemplateIsNotRegistered() {
new ApplicationContextRunner().withSystemProperties("spring.aot.enabled:true")
.withInitializer(this::applyTestRestTemplateContextCustomizer)
.run((context) -> {
assertThat(context).doesNotHaveBean(TestRestTemplateRegistrar.class);
assertThat(context).doesNotHaveBean(TestRestTemplate.class);
});
}
@SuppressWarnings({ "unchecked", "rawtypes" })
void applyTestRestTemplateContextCustomizer(ConfigurableApplicationContext context) {
MergedContextConfiguration configuration = mock(MergedContextConfiguration.class);
given(configuration.getTestClass()).willReturn((Class) TestClass.class);
new TestRestTemplateContextCustomizer().customizeContext(context, configuration);
}
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
static class TestClass {
}
static class TestApplicationContext extends AbstractApplicationContext {
private final ConfigurableListableBeanFactory beanFactory = new DefaultListableBeanFactory();
@Override
protected void refreshBeanFactory() {
}
@Override
protected void closeBeanFactory() {
}
@Override
public ConfigurableListableBeanFactory getBeanFactory() {
return this.beanFactory;
}
}
}

View File

@@ -1,505 +0,0 @@
/*
* Copyright 2012-2025 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.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Redirect;
import java.util.Base64;
import java.util.stream.Stream;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
import org.apache.hc.client5.http.impl.classic.RedirectExec;
import org.apache.hc.client5.http.protocol.RedirectStrategy;
import org.assertj.core.extractor.Extractors;
import org.junit.jupiter.api.Test;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.http.client.HttpRedirects;
import org.springframework.boot.test.web.client.TestRestTemplate.HttpClientOption;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.mock.http.client.MockClientHttpRequest;
import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodCallback;
import org.springframework.web.client.NoOpResponseErrorHandler;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link TestRestTemplate}.
*
* @author Dave Syer
* @author Phillip Webb
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Kristine Jetzke
* @author Yanming Zhou
*/
class TestRestTemplateTests {
@Test
void fromRestTemplateBuilder() {
RestTemplateBuilder builder = mock(RestTemplateBuilder.class);
RestTemplate delegate = new RestTemplate();
given(builder.build()).willReturn(delegate);
assertThat(new TestRestTemplate(builder).getRestTemplate()).isEqualTo(delegate);
}
@Test
void simple() {
// The Apache client is on the classpath so we get the fully-fledged factory
assertThat(new TestRestTemplate().getRestTemplate().getRequestFactory())
.isInstanceOf(HttpComponentsClientHttpRequestFactory.class);
}
@Test
void doNotReplaceCustomRequestFactory() {
RestTemplateBuilder builder = new RestTemplateBuilder().requestFactory(TestClientHttpRequestFactory.class);
TestRestTemplate testRestTemplate = new TestRestTemplate(builder);
assertThat(testRestTemplate.getRestTemplate().getRequestFactory())
.isInstanceOf(TestClientHttpRequestFactory.class);
}
@Test
void useTheSameRequestFactoryClassWithBasicAuth() {
TestClientHttpRequestFactory customFactory = new TestClientHttpRequestFactory();
RestTemplateBuilder builder = new RestTemplateBuilder().requestFactory(() -> customFactory);
TestRestTemplate testRestTemplate = new TestRestTemplate(builder).withBasicAuth("test", "test");
RestTemplate restTemplate = testRestTemplate.getRestTemplate();
assertThat(restTemplate.getRequestFactory()).isEqualTo(customFactory).hasSameClassAs(customFactory);
}
@Test
void getRootUriRootUriSetViaRestTemplateBuilder() {
String rootUri = "https://example.com";
RestTemplateBuilder delegate = new RestTemplateBuilder().rootUri(rootUri);
assertThat(new TestRestTemplate(delegate).getRootUri()).isEqualTo(rootUri);
}
@Test
void getRootUriRootUriSetViaLocalHostUriTemplateHandler() {
String rootUri = "https://example.com";
TestRestTemplate template = new TestRestTemplate();
LocalHostUriTemplateHandler templateHandler = mock(LocalHostUriTemplateHandler.class);
given(templateHandler.getRootUri()).willReturn(rootUri);
template.setUriTemplateHandler(templateHandler);
assertThat(template.getRootUri()).isEqualTo(rootUri);
}
@Test
void getRootUriRootUriNotSet() {
assertThat(new TestRestTemplate().getRootUri()).isEmpty();
}
@Test
void authenticated() {
TestRestTemplate restTemplate = new TestRestTemplate("user", "password");
assertBasicAuthorizationCredentials(restTemplate, "user", "password");
}
@Test
@SuppressWarnings("removal")
void options() {
RequestConfig config = getRequestConfig(
new TestRestTemplate(HttpClientOption.ENABLE_REDIRECTS, HttpClientOption.ENABLE_COOKIES));
assertThat(config.isRedirectsEnabled()).isTrue();
assertThat(config.getCookieSpec()).isEqualTo("strict");
}
@Test
void jdkBuilderCanBeSpecifiedWithSpecificRedirects() {
RestTemplateBuilder builder = new RestTemplateBuilder()
.requestFactoryBuilder(ClientHttpRequestFactoryBuilder.jdk());
TestRestTemplate templateWithRedirects = new TestRestTemplate(builder.redirects(HttpRedirects.FOLLOW));
assertThat(getJdkHttpClient(templateWithRedirects).followRedirects()).isEqualTo(Redirect.NORMAL);
TestRestTemplate templateWithoutRedirects = new TestRestTemplate(builder.redirects(HttpRedirects.DONT_FOLLOW));
assertThat(getJdkHttpClient(templateWithoutRedirects).followRedirects()).isEqualTo(Redirect.NEVER);
}
@Test
@SuppressWarnings("removal")
void httpComponentsAreBuiltConsideringSettingsInRestTemplateBuilder() {
RestTemplateBuilder builder = new RestTemplateBuilder()
.requestFactoryBuilder(ClientHttpRequestFactoryBuilder.httpComponents());
assertThat(getRedirectStrategy((RestTemplateBuilder) null)).matches(this::isFollowStrategy);
assertThat(getRedirectStrategy(null, HttpClientOption.ENABLE_REDIRECTS)).matches(this::isFollowStrategy);
assertThat(getRedirectStrategy(builder)).matches(this::isFollowStrategy);
assertThat(getRedirectStrategy(builder, HttpClientOption.ENABLE_REDIRECTS)).matches(this::isFollowStrategy);
assertThat(getRedirectStrategy(builder.redirects(HttpRedirects.DONT_FOLLOW)))
.matches(this::isDontFollowStrategy);
assertThat(getRedirectStrategy(builder.redirects(HttpRedirects.DONT_FOLLOW), HttpClientOption.ENABLE_REDIRECTS))
.matches(this::isFollowStrategy);
}
@Test
void withRequestFactorySettingsRedirectsForHttpComponents() {
TestRestTemplate template = new TestRestTemplate();
assertThat(getRedirectStrategy(template)).matches(this::isFollowStrategy);
assertThat(getRedirectStrategy(template.withRequestFactorySettings(
ClientHttpRequestFactorySettings.defaults().withRedirects(HttpRedirects.FOLLOW))))
.matches(this::isFollowStrategy);
assertThat(getRedirectStrategy(template.withRequestFactorySettings(
ClientHttpRequestFactorySettings.defaults().withRedirects(HttpRedirects.DONT_FOLLOW))))
.matches(this::isDontFollowStrategy);
}
@Test
void withRedirects() {
TestRestTemplate template = new TestRestTemplate();
assertThat(getRedirectStrategy(template)).matches(this::isFollowStrategy);
assertThat(getRedirectStrategy(template.withRedirects(HttpRedirects.FOLLOW))).matches(this::isFollowStrategy);
assertThat(getRedirectStrategy(template.withRedirects(HttpRedirects.DONT_FOLLOW)))
.matches(this::isDontFollowStrategy);
}
@Test
void withRequestFactorySettingsRedirectsForJdk() {
TestRestTemplate template = new TestRestTemplate(
new RestTemplateBuilder().requestFactoryBuilder(ClientHttpRequestFactoryBuilder.jdk()));
assertThat(getJdkHttpClient(template).followRedirects()).isEqualTo(Redirect.NORMAL);
assertThat(getJdkHttpClient(template.withRequestFactorySettings(
ClientHttpRequestFactorySettings.defaults().withRedirects(HttpRedirects.DONT_FOLLOW)))
.followRedirects()).isEqualTo(Redirect.NEVER);
}
@Test
void withRequestFactorySettingsUpdateRedirectsForJdk() {
TestRestTemplate template = new TestRestTemplate(
new RestTemplateBuilder().requestFactoryBuilder(ClientHttpRequestFactoryBuilder.jdk()));
assertThat(getJdkHttpClient(template).followRedirects()).isEqualTo(Redirect.NORMAL);
assertThat(getJdkHttpClient(
template.withRequestFactorySettings((settings) -> settings.withRedirects(HttpRedirects.DONT_FOLLOW)))
.followRedirects()).isEqualTo(Redirect.NEVER);
}
private RequestConfig getRequestConfig(TestRestTemplate template) {
ClientHttpRequestFactory requestFactory = template.getRestTemplate().getRequestFactory();
return (RequestConfig) Extractors.byName("httpClient.defaultConfig").apply(requestFactory);
}
private RedirectStrategy getRedirectStrategy(RestTemplateBuilder builder, HttpClientOption... httpClientOptions) {
builder = (builder != null) ? builder : new RestTemplateBuilder();
TestRestTemplate template = new TestRestTemplate(builder, null, null, httpClientOptions);
return getRedirectStrategy(template);
}
private RedirectStrategy getRedirectStrategy(TestRestTemplate template) {
ClientHttpRequestFactory requestFactory = template.getRestTemplate().getRequestFactory();
Object chain = Extractors.byName("httpClient.execChain").apply(requestFactory);
while (chain != null) {
Object handler = Extractors.byName("handler").apply(chain);
if (handler instanceof RedirectExec) {
return (RedirectStrategy) Extractors.byName("redirectStrategy").apply(handler);
}
chain = Extractors.byName("next").apply(chain);
}
return null;
}
private boolean isFollowStrategy(RedirectStrategy redirectStrategy) {
return redirectStrategy instanceof DefaultRedirectStrategy;
}
private boolean isDontFollowStrategy(RedirectStrategy redirectStrategy) {
return redirectStrategy.getClass().getName().contains("NoFollow");
}
private HttpClient getJdkHttpClient(TestRestTemplate template) {
JdkClientHttpRequestFactory requestFactory = (JdkClientHttpRequestFactory) template.getRestTemplate()
.getRequestFactory();
return (HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient");
}
@Test
void restOperationsAreAvailable() {
RestTemplate delegate = mock(RestTemplate.class);
given(delegate.getRequestFactory()).willReturn(new SimpleClientHttpRequestFactory());
given(delegate.getUriTemplateHandler()).willReturn(new DefaultUriBuilderFactory());
RestTemplateBuilder builder = mock(RestTemplateBuilder.class);
given(builder.build()).willReturn(delegate);
TestRestTemplate restTemplate = new TestRestTemplate(builder);
ReflectionUtils.doWithMethods(RestOperations.class, new MethodCallback() {
@Override
public void doWith(Method method) {
Method equivalent = ReflectionUtils.findMethod(TestRestTemplate.class, method.getName(),
method.getParameterTypes());
assertThat(equivalent).as("Method %s not found", method).isNotNull();
assertThat(Modifier.isPublic(equivalent.getModifiers()))
.as("Method %s should have been public", equivalent)
.isTrue();
try {
equivalent.invoke(restTemplate, mockArguments(method.getParameterTypes()));
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
private Object[] mockArguments(Class<?>[] parameterTypes) throws Exception {
Object[] arguments = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
arguments[i] = mockArgument(parameterTypes[i]);
}
return arguments;
}
@SuppressWarnings("rawtypes")
private Object mockArgument(Class<?> type) throws Exception {
if (String.class.equals(type)) {
return "String";
}
if (Object[].class.equals(type)) {
return new Object[0];
}
if (URI.class.equals(type)) {
return new URI("http://localhost");
}
if (HttpMethod.class.equals(type)) {
return HttpMethod.GET;
}
if (Class.class.equals(type)) {
return Object.class;
}
if (RequestEntity.class.equals(type)) {
return new RequestEntity(HttpMethod.GET, new URI("http://localhost"));
}
return mock(type);
}
}, (method) -> Modifier.isPublic(method.getModifiers()));
}
@Test
void withBasicAuthAddsBasicAuthWhenNotAlreadyPresent() {
TestRestTemplate original = new TestRestTemplate();
TestRestTemplate basicAuth = original.withBasicAuth("user", "password");
assertThat(getConverterClasses(original)).containsExactlyElementsOf(getConverterClasses(basicAuth).toList());
assertThat(basicAuth.getRestTemplate().getInterceptors()).isEmpty();
assertBasicAuthorizationCredentials(original, null, null);
assertBasicAuthorizationCredentials(basicAuth, "user", "password");
}
@Test
void withBasicAuthReplacesBasicAuthWhenAlreadyPresent() {
TestRestTemplate original = new TestRestTemplate("foo", "bar").withBasicAuth("replace", "replace");
TestRestTemplate basicAuth = original.withBasicAuth("user", "password");
assertThat(getConverterClasses(basicAuth)).containsExactlyElementsOf(getConverterClasses(original).toList());
assertBasicAuthorizationCredentials(original, "replace", "replace");
assertBasicAuthorizationCredentials(basicAuth, "user", "password");
}
private Stream<Class<?>> getConverterClasses(TestRestTemplate testRestTemplate) {
return testRestTemplate.getRestTemplate().getMessageConverters().stream().map(Object::getClass);
}
@Test
void withBasicAuthShouldUseNoOpErrorHandler() {
TestRestTemplate originalTemplate = new TestRestTemplate("foo", "bar");
ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class);
originalTemplate.getRestTemplate().setErrorHandler(errorHandler);
TestRestTemplate basicAuthTemplate = originalTemplate.withBasicAuth("user", "password");
assertThat(basicAuthTemplate.getRestTemplate().getErrorHandler()).isInstanceOf(NoOpResponseErrorHandler.class);
}
@Test
void exchangeWithRelativeTemplatedUrlRequestEntity() throws Exception {
RequestEntity<Void> entity = RequestEntity.get("/a/b/c.{ext}", "txt").build();
TestRestTemplate template = new TestRestTemplate();
ClientHttpRequestFactory requestFactory = mock(ClientHttpRequestFactory.class);
MockClientHttpRequest request = new MockClientHttpRequest();
request.setResponse(new MockClientHttpResponse(new byte[0], HttpStatus.OK));
URI absoluteUri = URI.create("http://localhost:8080/a/b/c.txt");
given(requestFactory.createRequest(eq(absoluteUri), eq(HttpMethod.GET))).willReturn(request);
template.getRestTemplate().setRequestFactory(requestFactory);
LocalHostUriTemplateHandler uriTemplateHandler = new LocalHostUriTemplateHandler(new MockEnvironment());
template.setUriTemplateHandler(uriTemplateHandler);
template.exchange(entity, String.class);
then(requestFactory).should().createRequest(eq(absoluteUri), eq(HttpMethod.GET));
}
@Test
void exchangeWithAbsoluteTemplatedUrlRequestEntity() throws Exception {
RequestEntity<Void> entity = RequestEntity.get("https://api.example.com/a/b/c.{ext}", "txt").build();
TestRestTemplate template = new TestRestTemplate();
ClientHttpRequestFactory requestFactory = mock(ClientHttpRequestFactory.class);
MockClientHttpRequest request = new MockClientHttpRequest();
request.setResponse(new MockClientHttpResponse(new byte[0], HttpStatus.OK));
URI absoluteUri = URI.create("https://api.example.com/a/b/c.txt");
given(requestFactory.createRequest(eq(absoluteUri), eq(HttpMethod.GET))).willReturn(request);
template.getRestTemplate().setRequestFactory(requestFactory);
template.exchange(entity, String.class);
then(requestFactory).should().createRequest(eq(absoluteUri), eq(HttpMethod.GET));
}
@Test
void deleteHandlesRelativeUris() throws IOException {
verifyRelativeUriHandling(TestRestTemplate::delete);
}
@Test
void exchangeWithRequestEntityAndClassHandlesRelativeUris() throws IOException {
verifyRelativeUriHandling((testRestTemplate, relativeUri) -> testRestTemplate
.exchange(new RequestEntity<>(HttpMethod.GET, relativeUri), String.class));
}
@Test
void exchangeWithRequestEntityAndParameterizedTypeReferenceHandlesRelativeUris() throws IOException {
verifyRelativeUriHandling((testRestTemplate, relativeUri) -> testRestTemplate
.exchange(new RequestEntity<>(HttpMethod.GET, relativeUri), new ParameterizedTypeReference<String>() {
}));
}
@Test
void exchangeHandlesRelativeUris() throws IOException {
verifyRelativeUriHandling((testRestTemplate, relativeUri) -> testRestTemplate.exchange(relativeUri,
HttpMethod.GET, new HttpEntity<>(new byte[0]), String.class));
}
@Test
void exchangeWithParameterizedTypeReferenceHandlesRelativeUris() throws IOException {
verifyRelativeUriHandling((testRestTemplate, relativeUri) -> testRestTemplate.exchange(relativeUri,
HttpMethod.GET, new HttpEntity<>(new byte[0]), new ParameterizedTypeReference<String>() {
}));
}
@Test
void executeHandlesRelativeUris() throws IOException {
verifyRelativeUriHandling(
(testRestTemplate, relativeUri) -> testRestTemplate.execute(relativeUri, HttpMethod.GET, null, null));
}
@Test
void getForEntityHandlesRelativeUris() throws IOException {
verifyRelativeUriHandling(
(testRestTemplate, relativeUri) -> testRestTemplate.getForEntity(relativeUri, String.class));
}
@Test
void getForObjectHandlesRelativeUris() throws IOException {
verifyRelativeUriHandling(
(testRestTemplate, relativeUri) -> testRestTemplate.getForObject(relativeUri, String.class));
}
@Test
void headForHeadersHandlesRelativeUris() throws IOException {
verifyRelativeUriHandling(TestRestTemplate::headForHeaders);
}
@Test
void optionsForAllowHandlesRelativeUris() throws IOException {
verifyRelativeUriHandling(TestRestTemplate::optionsForAllow);
}
@Test
void patchForObjectHandlesRelativeUris() throws IOException {
verifyRelativeUriHandling(
(testRestTemplate, relativeUri) -> testRestTemplate.patchForObject(relativeUri, "hello", String.class));
}
@Test
void postForEntityHandlesRelativeUris() throws IOException {
verifyRelativeUriHandling(
(testRestTemplate, relativeUri) -> testRestTemplate.postForEntity(relativeUri, "hello", String.class));
}
@Test
void postForLocationHandlesRelativeUris() throws IOException {
verifyRelativeUriHandling(
(testRestTemplate, relativeUri) -> testRestTemplate.postForLocation(relativeUri, "hello"));
}
@Test
void postForObjectHandlesRelativeUris() throws IOException {
verifyRelativeUriHandling(
(testRestTemplate, relativeUri) -> testRestTemplate.postForObject(relativeUri, "hello", String.class));
}
@Test
void putHandlesRelativeUris() throws IOException {
verifyRelativeUriHandling((testRestTemplate, relativeUri) -> testRestTemplate.put(relativeUri, "hello"));
}
private void verifyRelativeUriHandling(TestRestTemplateCallback callback) throws IOException {
ClientHttpRequestFactory requestFactory = mock(ClientHttpRequestFactory.class);
MockClientHttpRequest request = new MockClientHttpRequest();
request.setResponse(new MockClientHttpResponse(new byte[0], HttpStatus.OK));
URI absoluteUri = URI.create("http://localhost:8080/a/b/c.txt?param=%7Bsomething%7D");
given(requestFactory.createRequest(eq(absoluteUri), any(HttpMethod.class))).willReturn(request);
TestRestTemplate template = new TestRestTemplate();
template.getRestTemplate().setRequestFactory(requestFactory);
LocalHostUriTemplateHandler uriTemplateHandler = new LocalHostUriTemplateHandler(new MockEnvironment());
template.setUriTemplateHandler(uriTemplateHandler);
callback.doWithTestRestTemplate(template, URI.create("/a/b/c.txt?param=%7Bsomething%7D"));
then(requestFactory).should().createRequest(eq(absoluteUri), any(HttpMethod.class));
}
private void assertBasicAuthorizationCredentials(TestRestTemplate testRestTemplate, String username,
String password) {
ClientHttpRequest request = ReflectionTestUtils.invokeMethod(testRestTemplate.getRestTemplate(),
"createRequest", URI.create("http://localhost"), HttpMethod.POST);
if (username == null) {
assertThat(request.getHeaders().headerNames()).doesNotContain(HttpHeaders.AUTHORIZATION);
}
else {
assertThat(request.getHeaders().headerNames()).contains(HttpHeaders.AUTHORIZATION);
assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).containsExactly("Basic "
+ Base64.getEncoder().encodeToString(String.format("%s:%s", username, password).getBytes()));
}
}
interface TestRestTemplateCallback {
void doWithTestRestTemplate(TestRestTemplate testRestTemplate, URI relativeUri);
}
static class TestClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
}
}

View File

@@ -1,87 +0,0 @@
/*
* Copyright 2012-2025 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.htmlunit;
import java.io.IOException;
import java.net.URL;
import org.htmlunit.StringWebResponse;
import org.htmlunit.WebClient;
import org.htmlunit.WebConnection;
import org.htmlunit.WebResponse;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.assertArg;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link LocalHostWebClient}.
*
* @author Phillip Webb
*/
@SuppressWarnings("resource")
@ExtendWith(MockitoExtension.class)
class LocalHostWebClientTests {
@Test
void createWhenEnvironmentIsNullWillThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> new LocalHostWebClient(null))
.withMessageContaining("'environment' must not be null");
}
@Test
void getPageWhenUrlIsRelativeAndNoPortWillUseLocalhost8080() throws Exception {
MockEnvironment environment = new MockEnvironment();
WebClient client = new LocalHostWebClient(environment);
WebConnection connection = mockConnection();
client.setWebConnection(connection);
client.getPage("/test");
URL expectedUrl = new URL("http://localhost:8080/test");
then(connection).should()
.getResponse(assertArg((request) -> assertThat(request.getUrl()).isEqualTo(expectedUrl)));
}
@Test
void getPageWhenUrlIsRelativeAndHasPortWillUseLocalhostPort() throws Exception {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("local.server.port", "8181");
WebClient client = new LocalHostWebClient(environment);
WebConnection connection = mockConnection();
client.setWebConnection(connection);
client.getPage("/test");
URL expectedUrl = new URL("http://localhost:8181/test");
then(connection).should()
.getResponse(assertArg((request) -> assertThat(request.getUrl()).isEqualTo(expectedUrl)));
}
private WebConnection mockConnection() throws IOException {
WebConnection connection = mock(WebConnection.class);
WebResponse response = new StringWebResponse("test", new URL("http://localhost"));
given(connection.getResponse(any())).willReturn(response);
return connection;
}
}

View File

@@ -1,144 +0,0 @@
/*
* Copyright 2012-2025 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.htmlunit.webdriver;
import java.net.URL;
import org.htmlunit.BrowserVersion;
import org.htmlunit.TopLevelWindow;
import org.htmlunit.WebClient;
import org.htmlunit.WebClientOptions;
import org.htmlunit.WebConsole;
import org.htmlunit.WebRequest;
import org.htmlunit.WebWindow;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openqa.selenium.Capabilities;
import org.springframework.core.env.Environment;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link LocalHostWebConnectionHtmlUnitDriver}.
*
* @author Phillip Webb
*/
@ExtendWith(MockitoExtension.class)
class LocalHostWebConnectionHtmlUnitDriverTests {
private final WebClient webClient;
LocalHostWebConnectionHtmlUnitDriverTests(@Mock WebClient webClient) {
this.webClient = webClient;
given(this.webClient.getOptions()).willReturn(new WebClientOptions());
given(this.webClient.getWebConsole()).willReturn(new WebConsole());
WebWindow currentWindow = mock(WebWindow.class);
given(currentWindow.isClosed()).willReturn(false);
given(this.webClient.getCurrentWindow()).willReturn(currentWindow);
}
@Test
void createWhenEnvironmentIsNullWillThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> new LocalHostWebConnectionHtmlUnitDriver(null))
.withMessageContaining("'environment' must not be null");
}
@Test
void createWithJavascriptFlagWhenEnvironmentIsNullWillThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> new LocalHostWebConnectionHtmlUnitDriver(null, true))
.withMessageContaining("'environment' must not be null");
}
@Test
void createWithBrowserVersionWhenEnvironmentIsNullWillThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new LocalHostWebConnectionHtmlUnitDriver(null, BrowserVersion.CHROME))
.withMessageContaining("'environment' must not be null");
}
@Test
void createWithCapabilitiesWhenEnvironmentIsNullWillThrowException() {
Capabilities capabilities = mock(Capabilities.class);
given(capabilities.getBrowserName()).willReturn("htmlunit");
given(capabilities.getBrowserVersion()).willReturn("chrome");
assertThatIllegalArgumentException()
.isThrownBy(() -> new LocalHostWebConnectionHtmlUnitDriver(null, capabilities))
.withMessageContaining("'environment' must not be null");
}
@Test
void getWhenUrlIsRelativeAndNoPortWillUseLocalhost8080() throws Exception {
MockEnvironment environment = new MockEnvironment();
LocalHostWebConnectionHtmlUnitDriver driver = new TestLocalHostWebConnectionHtmlUnitDriver(environment);
driver.get("/test");
then(this.webClient).should()
.getPage(any(TopLevelWindow.class), requestToUrl(new URL("http://localhost:8080/test")));
}
@Test
void getWhenUrlIsRelativeAndHasPortWillUseLocalhostPort() throws Exception {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("local.server.port", "8181");
LocalHostWebConnectionHtmlUnitDriver driver = new TestLocalHostWebConnectionHtmlUnitDriver(environment);
driver.get("/test");
then(this.webClient).should()
.getPage(any(TopLevelWindow.class), requestToUrl(new URL("http://localhost:8181/test")));
}
private WebRequest requestToUrl(URL url) {
return argThat(new WebRequestUrlArgumentMatcher(url));
}
public class TestLocalHostWebConnectionHtmlUnitDriver extends LocalHostWebConnectionHtmlUnitDriver {
TestLocalHostWebConnectionHtmlUnitDriver(Environment environment) {
super(environment);
}
@Override
public WebClient getWebClient() {
return LocalHostWebConnectionHtmlUnitDriverTests.this.webClient;
}
}
private static final class WebRequestUrlArgumentMatcher implements ArgumentMatcher<WebRequest> {
private final URL expectedUrl;
private WebRequestUrlArgumentMatcher(URL expectedUrl) {
this.expectedUrl = expectedUrl;
}
@Override
public boolean matches(WebRequest argument) {
return argument.getUrl().equals(this.expectedUrl);
}
}
}

View File

@@ -1,92 +0,0 @@
/*
* 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.reactive.server;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.web.reactive.server.WebTestClientContextCustomizer.WebTestClientRegistrar;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link WebTestClientContextCustomizer}.
*
* @author Moritz Halbritter
*/
class WebTestClientContextCustomizerTests {
@Test
void whenContextIsNotABeanDefinitionRegistryWebTestClientIsRegistered() {
new ApplicationContextRunner(TestApplicationContext::new)
.withInitializer(this::applyWebTestClientContextCustomizer)
.run((context) -> assertThat(context).hasSingleBean(WebTestClient.class));
}
@Test
void whenUsingAotGeneratedArtifactsWebTestClientIsNotRegistered() {
new ApplicationContextRunner().withSystemProperties("spring.aot.enabled:true")
.withInitializer(this::applyWebTestClientContextCustomizer)
.run((context) -> {
assertThat(context).doesNotHaveBean(WebTestClientRegistrar.class);
assertThat(context).doesNotHaveBean(WebTestClient.class);
});
}
@SuppressWarnings({ "unchecked", "rawtypes" })
void applyWebTestClientContextCustomizer(ConfigurableApplicationContext context) {
MergedContextConfiguration configuration = mock(MergedContextConfiguration.class);
given(configuration.getTestClass()).willReturn((Class) TestClass.class);
new WebTestClientContextCustomizer().customizeContext(context, configuration);
}
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
static class TestClass {
}
static class TestApplicationContext extends AbstractApplicationContext {
private final ConfigurableListableBeanFactory beanFactory = new DefaultListableBeanFactory();
@Override
protected void refreshBeanFactory() {
}
@Override
protected void closeBeanFactory() {
}
@Override
public ConfigurableListableBeanFactory getBeanFactory() {
return this.beanFactory;
}
}
}

View File

@@ -1,52 +0,0 @@
/*
* Copyright 2012-2024 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.reactive.server;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import org.springframework.test.context.ContextCustomizer;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link WebTestClientContextCustomizerFactory} when spring webflux is not on
* the classpath.
*
* @author Tobias Gesellchen
* @author Stephane Nicoll
*/
@ClassPathExclusions("spring-webflux*.jar")
class WebTestClientContextCustomizerWithoutWebfluxIntegrationTests {
@Test
void customizerIsNotCreatedWithoutWebClient() {
WebTestClientContextCustomizerFactory contextCustomizerFactory = new WebTestClientContextCustomizerFactory();
ContextCustomizer contextCustomizer = contextCustomizerFactory.createContextCustomizer(TestClass.class,
Collections.emptyList());
assertThat(contextCustomizer).isNull();
}
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
private static final class TestClass {
}
}

View File

@@ -1,55 +0,0 @@
/*
* Copyright 2012-2024 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.reactor.netty;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ReactorResourceFactory;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactory}.
*
* @author Phillip Webb
*/
@SpringJUnitConfig
class DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactoryTests {
@Autowired
private ReactorResourceFactory reactorResourceFactory;
@Test
void disablesUseGlobalResources() {
assertThat(this.reactorResourceFactory.isUseGlobalResources()).isFalse();
}
@Configuration
static class Config {
@Bean
ReactorResourceFactory reactorResourceFactory() {
return new ReactorResourceFactory();
}
}
}

View File

@@ -1,54 +0,0 @@
/*
* Copyright 2012-2022 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.server;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link LocalManagementPort @LocalManagementPort}.
*
* @author Andy Wilkinson
*/
@ExtendWith(SpringExtension.class)
@TestPropertySource(properties = "local.management.port=8181")
class LocalManagementPortTests {
@Value("${local.management.port}")
private String fromValue;
@LocalManagementPort
private String fromAnnotation;
@Test
void testLocalManagementPortAnnotation() {
assertThat(this.fromAnnotation).isNotNull().isEqualTo(this.fromValue);
}
@Configuration(proxyBeanMethods = false)
static class Config {
}
}

View File

@@ -1,55 +0,0 @@
/*
* Copyright 2012-2022 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.server;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link LocalServerPort @LocalServerPort}.
*
* @author Anand Shah
* @author Phillip Webb
*/
@ExtendWith(SpringExtension.class)
@TestPropertySource(properties = "local.server.port=8181")
class LocalServerPortTests {
@Value("${local.server.port}")
private String fromValue;
@LocalServerPort
private String fromAnnotation;
@Test
void testLocalServerPortAnnotation() {
assertThat(this.fromAnnotation).isNotNull().isEqualTo(this.fromValue);
}
@Configuration(proxyBeanMethods = false)
static class Config {
}
}

View File

@@ -1,265 +0,0 @@
/*
* Copyright 2012-2025 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 io.mockk.mockk
import io.mockk.verify
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.core.ParameterizedTypeReference
import org.springframework.http.HttpEntity
import org.springframework.http.HttpMethod
import org.springframework.http.RequestEntity
import org.springframework.util.ReflectionUtils
import org.springframework.web.client.RestOperations
import java.net.URI
import kotlin.reflect.full.createType
import kotlin.reflect.jvm.kotlinFunction
/**
* Mock object based tests for [TestRestTemplate] Kotlin extensions
*
* @author Sebastien Deleuze
*/
class TestRestTemplateExtensionsTests {
val template = mockk<TestRestTemplate>(relaxed = true)
@Test
fun `getForObject with reified type parameters, String and varargs`() {
val url = "https://spring.io"
val var1 = "var1"
val var2 = "var2"
template.getForObject<Foo>(url, var1, var2)
template.restTemplate
verify(exactly = 1) { template.getForObject(url, Foo::class.java, var1, var2) }
}
@Test
fun `getForObject with reified type parameters, String and Map`() {
val url = "https://spring.io"
val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2"))
template.getForObject<Foo>(url, vars)
verify(exactly = 1) { template.getForObject(url, Foo::class.java, vars) }
}
@Test
fun `getForObject with reified type parameters and URI`() {
val url = URI("https://spring.io")
template.getForObject<Foo>(url)
verify(exactly = 1) { template.getForObject(url, Foo::class.java) }
}
@Test
fun `getForEntity with reified type parameters and URI`() {
val url = URI("https://spring.io")
template.getForEntity<Foo>(url)
verify(exactly = 1) { template.getForEntity(url, Foo::class.java) }
}
@Test
fun `getForEntity with reified type parameters, String and varargs`() {
val url = "https://spring.io"
val var1 = "var1"
val var2 = "var2"
template.getForEntity<Foo>(url, var1, var2)
verify(exactly = 1) { template.getForEntity(url, Foo::class.java, var1, var2) }
}
@Test
fun `getForEntity with reified type parameters, String and Map`() {
val url = "https://spring.io"
val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2"))
template.getForEntity<Foo>(url, vars)
verify(exactly = 1) { template.getForEntity(url, Foo::class.java, vars) }
}
@Test
fun `patchForObject with reified type parameters, String, Any and varargs`() {
val url = "https://spring.io"
val body: Any = "body"
val var1 = "var1"
val var2 = "var2"
template.patchForObject<Foo>(url, body, var1, var2)
verify(exactly = 1) { template.patchForObject(url, body, Foo::class.java, var1, var2) }
}
@Test
fun `patchForObject with reified type parameters, String, Any and Map`() {
val url = "https://spring.io"
val body: Any = "body"
val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2"))
template.patchForObject<Foo>(url, body, vars)
verify(exactly = 1) { template.patchForObject(url, body, Foo::class.java, vars) }
}
@Test
fun `patchForObject with reified type parameters, String and Any`() {
val url = "https://spring.io"
val body: Any = "body"
template.patchForObject<Foo>(url, body)
verify(exactly = 1) { template.patchForObject(url, body, Foo::class.java) }
}
@Test
fun `patchForObject with reified type parameters`() {
val url = "https://spring.io"
template.patchForObject<Foo>(url)
verify(exactly = 1) { template.patchForObject(url, null, Foo::class.java) }
}
@Test
fun `postForObject with reified type parameters, String, Any and varargs`() {
val url = "https://spring.io"
val body: Any = "body"
val var1 = "var1"
val var2 = "var2"
template.postForObject<Foo>(url, body, var1, var2)
verify(exactly = 1) { template.postForObject(url, body, Foo::class.java, var1, var2) }
}
@Test
fun `postForObject with reified type parameters, String, Any and Map`() {
val url = "https://spring.io"
val body: Any = "body"
val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2"))
template.postForObject<Foo>(url, body, vars)
verify(exactly = 1) { template.postForObject(url, body, Foo::class.java, vars) }
}
@Test
fun `postForObject with reified type parameters, String and Any`() {
val url = "https://spring.io"
val body: Any = "body"
template.postForObject<Foo>(url, body)
verify(exactly = 1) { template.postForObject(url, body, Foo::class.java) }
}
@Test
fun `postForObject with reified type parameters`() {
val url = "https://spring.io"
template.postForObject<Foo>(url)
verify(exactly = 1) { template.postForObject(url, null, Foo::class.java) }
}
@Test
fun `postForEntity with reified type parameters, String, Any and varargs`() {
val url = "https://spring.io"
val body: Any = "body"
val var1 = "var1"
val var2 = "var2"
template.postForEntity<Foo>(url, body, var1, var2)
verify(exactly = 1) { template.postForEntity(url, body, Foo::class.java, var1, var2) }
}
@Test
fun `postForEntity with reified type parameters, String, Any and Map`() {
val url = "https://spring.io"
val body: Any = "body"
val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2"))
template.postForEntity<Foo>(url, body, vars)
verify(exactly = 1) { template.postForEntity(url, body, Foo::class.java, vars) }
}
@Test
fun `postForEntity with reified type parameters, String and Any`() {
val url = "https://spring.io"
val body: Any = "body"
template.postForEntity<Foo>(url, body)
verify(exactly = 1) { template.postForEntity(url, body, Foo::class.java) }
}
@Test
fun `postForEntity with reified type parameters`() {
val url = "https://spring.io"
template.postForEntity<Foo>(url)
verify(exactly = 1) { template.postForEntity(url, null, Foo::class.java) }
}
@Test
fun `exchange with reified type parameters, String, HttpMethod, HttpEntity and varargs`() {
val url = "https://spring.io"
val method = HttpMethod.GET
val entity = mockk<HttpEntity<Foo>>()
val var1 = "var1"
val var2 = "var2"
template.exchange<List<Foo>>(url, method, entity, var1, var2)
verify(exactly = 1) { template.exchange(url, method, entity,
object : ParameterizedTypeReference<List<Foo>>() {}, var1, var2) }
}
@Test
fun `exchange with reified type parameters, String, HttpMethod, HttpEntity and Map`() {
val url = "https://spring.io"
val method = HttpMethod.GET
val entity = mockk<HttpEntity<Foo>>()
val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2"))
template.exchange<List<Foo>>(url, method, entity, vars)
verify(exactly = 1) { template.exchange(url, method, entity,
object : ParameterizedTypeReference<List<Foo>>() {}, vars) }
}
@Test
fun `exchange with reified type parameters, String, HttpMethod, HttpEntity`() {
val url = "https://spring.io"
val method = HttpMethod.GET
val entity = mockk<HttpEntity<Foo>>()
template.exchange<List<Foo>>(url, method, entity)
verify(exactly = 1) { template.exchange(url, method, entity,
object : ParameterizedTypeReference<List<Foo>>() {}) }
}
@Test
fun `exchange with reified type parameters and HttpEntity`() {
val entity = mockk<RequestEntity<Foo>>()
template.exchange<List<Foo>>(entity)
verify(exactly = 1) { template.exchange(entity,
object : ParameterizedTypeReference<List<Foo>>() {}) }
}
@Test
fun `exchange with reified type parameters, String and HttpMethod`() {
val url = "https://spring.io"
val method = HttpMethod.GET
template.exchange<List<Foo>>(url, method)
verify(exactly = 1) { template.exchange(url, method, null,
object : ParameterizedTypeReference<List<Foo>>() {}) }
}
@Test
fun `RestOperations are available`() {
val extensions = Class.forName(
"org.springframework.boot.test.web.client.TestRestTemplateExtensionsKt")
ReflectionUtils.doWithMethods(RestOperations::class.java) { method ->
arrayOf(ParameterizedTypeReference::class, Class::class).forEach { kClass ->
if (method.parameterTypes.contains(kClass.java)) {
val parameters = mutableListOf<Class<*>>(TestRestTemplate::class.java)
.apply { addAll(method.parameterTypes.filter { it != kClass.java }) }
val f = extensions.getDeclaredMethod(method.name,
*parameters.toTypedArray()).kotlinFunction!!
assertThat(f.typeParameters.size).isEqualTo(1)
assertThat(listOf(Any::class.createType()))
.isEqualTo(f.typeParameters[0].upperBounds)
}
}
}
}
class Foo
}