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

@@ -0,0 +1,41 @@
/*
* 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.web.server.test;
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 4.0.0
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Value("${local.management.port}")
public @interface LocalManagementPort {
}

View File

@@ -0,0 +1,42 @@
/*
* 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.web.server.test;
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 4.0.0
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Value("${local.server.port}")
public @interface LocalServerPort {
}

View File

@@ -0,0 +1,108 @@
/*
* 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.web.server.test;
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

@@ -0,0 +1,88 @@
/*
* 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.web.server.test.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 4.0.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

@@ -0,0 +1,187 @@
/*
* 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.web.server.test.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.web.client.RestTemplateBuilder;
import org.springframework.boot.web.server.AbstractConfigurableWebServerFactory;
import org.springframework.boot.web.server.test.client.TestRestTemplate.HttpClientOption;
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

@@ -0,0 +1,43 @@
/*
* 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.web.server.test.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

@@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Client-side support for testing embedded web servers.
*/
package org.springframework.boot.web.server.test.client;

View File

@@ -0,0 +1,38 @@
/*
* 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.web.server.test.client.reactive;
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 4.0.0
*/
@FunctionalInterface
public interface WebTestClientBuilderCustomizer {
/**
* Customize the given {@code builder}.
* @param builder the builder
*/
void customize(Builder builder);
}

View File

@@ -0,0 +1,246 @@
/*
* 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.web.server.test.client.reactive;
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

@@ -0,0 +1,52 @@
/*
* 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.web.server.test.client.reactive;
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

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

View File

@@ -0,0 +1,53 @@
/*
* 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.web.server.test.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 4.0.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

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

View File

@@ -0,0 +1,69 @@
/*
* 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.web.server.test.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 4.0.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

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

View File

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

View File

@@ -0,0 +1,39 @@
/*
* 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.web.server.test.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

@@ -0,0 +1,74 @@
/*
* 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.web.server.test.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

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

View File

@@ -0,0 +1,279 @@
/*
* 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.web.server.test.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 4.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

@@ -0,0 +1,9 @@
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.web.server.test.SpringBootTestRandomPortEnvironmentPostProcessor
# Spring Test Context Customizer Factories
org.springframework.test.context.ContextCustomizerFactory=\
org.springframework.boot.web.server.test.client.TestRestTemplateContextCustomizerFactory,\
org.springframework.boot.web.server.test.client.reactive.WebTestClientContextCustomizerFactory,\
org.springframework.boot.web.server.test.reactor.netty.DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactory

View File

@@ -0,0 +1,129 @@
/*
* 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.web.server.test;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.tomcat.reactive.TomcatReactiveWebServerFactory;
import org.springframework.boot.web.context.reactive.ReactiveWebApplicationContext;
import org.springframework.boot.web.server.reactive.ReactiveWebServerFactory;
import org.springframework.boot.web.server.test.client.TestRestTemplate;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Base class for {@link SpringBootTest @SpringBootTest} tests configured to start an
* embedded reactive container.
*
* @author Stephane Nicoll
*/
abstract class AbstractSpringBootTestEmbeddedReactiveWebEnvironmentTests {
@LocalServerPort
private int port = 0;
@Value("${value}")
private int value = 0;
@Autowired
private ReactiveWebApplicationContext context;
@Autowired
private WebTestClient webClient;
@Autowired
private TestRestTemplate restTemplate;
ReactiveWebApplicationContext getContext() {
return this.context;
}
@Test
void runAndTestHttpEndpoint() {
assertThat(this.port).isNotEqualTo(8080).isNotZero();
WebTestClient.bindToServer()
.baseUrl("http://localhost:" + this.port)
.responseTimeout(Duration.ofMinutes(5))
.build()
.get()
.uri("/")
.exchange()
.expectBody(String.class)
.isEqualTo("Hello World");
}
@Test
void injectWebTestClient() {
this.webClient.get().uri("/").exchange().expectBody(String.class).isEqualTo("Hello World");
}
@Test
void injectTestRestTemplate() {
String body = this.restTemplate.getForObject("/", String.class);
assertThat(body).isEqualTo("Hello World");
}
@Test
void annotationAttributesOverridePropertiesFile() {
assertThat(this.value).isEqualTo(123);
}
@Configuration(proxyBeanMethods = false)
static class AbstractConfig {
@Value("${server.port:8080}")
private int port = 8080;
@Bean
HttpHandler httpHandler(ApplicationContext applicationContext) {
return WebHttpHandlerBuilder.applicationContext(applicationContext).build();
}
@Bean
ReactiveWebServerFactory webServerFactory() {
TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory();
factory.setPort(this.port);
return factory;
}
@Bean
static PropertySourcesPlaceholderConfigurer propertyPlaceholder() {
return new PropertySourcesPlaceholderConfigurer();
}
@RequestMapping("/")
Mono<String> home() {
return Mono.just("Hello World");
}
}
}

View File

@@ -0,0 +1,124 @@
/*
* 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.web.server.test;
import jakarta.servlet.ServletContext;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.servlet.ServletWebServerFactory;
import org.springframework.boot.web.server.test.client.TestRestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.DispatcherServlet;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Base class for {@link SpringBootTest @SpringBootTest} tests configured to start an
* embedded web server.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
abstract class AbstractSpringBootTestWebServerWebEnvironmentTests {
@LocalServerPort
private int port = 0;
@Value("${value}")
private int value = 0;
@Autowired
private WebApplicationContext context;
@Autowired
private ServletContext servletContext;
@Autowired
private TestRestTemplate restTemplate;
WebApplicationContext getContext() {
return this.context;
}
TestRestTemplate getRestTemplate() {
return this.restTemplate;
}
@Test
void runAndTestHttpEndpoint() {
assertThat(this.port).isNotEqualTo(8080).isNotZero();
String body = new RestTemplate().getForObject("http://localhost:" + this.port + "/", String.class);
assertThat(body).isEqualTo("Hello World");
}
@Test
void injectTestRestTemplate() {
String body = this.restTemplate.getForObject("/", String.class);
assertThat(body).isEqualTo("Hello World");
}
@Test
void annotationAttributesOverridePropertiesFile() {
assertThat(this.value).isEqualTo(123);
}
@Test
void validateWebApplicationContextIsSet() {
assertThat(this.context).isSameAs(WebApplicationContextUtils.getWebApplicationContext(this.servletContext));
}
@Configuration(proxyBeanMethods = false)
static class AbstractConfig {
@Value("${server.port:8080}")
private int port = 8080;
@Bean
DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean
ServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.setPort(this.port);
return factory;
}
@Bean
static PropertySourcesPlaceholderConfigurer propertyPlaceholder() {
return new PropertySourcesPlaceholderConfigurer();
}
@RequestMapping("/")
String home() {
return "Hello World";
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.web.server.test;
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

@@ -0,0 +1,55 @@
/*
* 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.web.server.test;
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

@@ -0,0 +1,214 @@
/*
* 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.web.server.test;
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

@@ -0,0 +1,45 @@
/*
* 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.web.server.test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.config.EnableWebFlux;
/**
* Tests for {@link SpringBootTest @SpringBootTest} in a reactive environment configured
* with {@link WebEnvironment#DEFINED_PORT}.
*
* @author Stephane Nicoll
*/
@DirtiesContext
@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT,
properties = { "spring.main.web-application-type=reactive", "server.port=0", "value=123" })
public class SpringBootTestReactiveWebEnvironmentDefinedPortTests
extends AbstractSpringBootTestEmbeddedReactiveWebEnvironmentTests {
@Configuration(proxyBeanMethods = false)
@EnableWebFlux
@RestController
static class Config extends AbstractConfig {
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.web.server.test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.config.EnableWebFlux;
/**
* Tests for {@link SpringBootTest @SpringBootTest} in a reactive environment configured
* with {@link WebEnvironment#RANDOM_PORT}.
*
* @author Stephane Nicoll
*/
@DirtiesContext
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
properties = { "spring.main.webApplicationType=reactive", "value=123" })
public class SpringBootTestReactiveWebEnvironmentRandomPortTests
extends AbstractSpringBootTestEmbeddedReactiveWebEnvironmentTests {
@Configuration(proxyBeanMethods = false)
@EnableWebFlux
@RestController
static class Config extends AbstractConfig {
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.web.server.test;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.config.EnableWebFlux;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SpringBootTest @SpringBootTest} in a reactive environment configured
* with a user-defined {@link RestTemplate} that is named {@code testRestTemplate}.
*
* @author Madhura Bhave
*/
@DirtiesContext
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
properties = { "spring.main.web-application-type=reactive", "value=123" })
class SpringBootTestReactiveWebEnvironmentUserDefinedTestRestTemplateTests
extends AbstractSpringBootTestEmbeddedReactiveWebEnvironmentTests {
@Test
void restTemplateIsUserDefined() {
assertThat(getContext().getBean("testRestTemplate")).isInstanceOf(RestTemplate.class);
}
@Configuration(proxyBeanMethods = false)
@EnableWebFlux
@RestController
static class Config extends AbstractConfig {
@Bean
RestTemplate testRestTemplate() {
return new RestTemplate();
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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.web.server.test;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SpringBootTest @SpringBootTest} configured with a user-defined
* {@link RestTemplate} that is named {@code testRestTemplate}.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
@DirtiesContext
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { "value=123" })
class SpringBootTestUserDefinedTestRestTemplateTests extends AbstractSpringBootTestWebServerWebEnvironmentTests {
@Test
void restTemplateIsUserDefined() {
assertThat(getContext().getBean("testRestTemplate")).isInstanceOf(RestTemplate.class);
}
// gh-7711
@Configuration(proxyBeanMethods = false)
@EnableWebMvc
@RestController
static class Config extends AbstractConfig {
@Bean
RestTemplate testRestTemplate() {
return new RestTemplate();
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.web.server.test;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.test.AbstractSpringBootTestWebServerWebEnvironmentTests.AbstractConfig;
import org.springframework.boot.web.server.test.SpringBootTestWebEnvironmentContextHierarchyTests.ChildConfiguration;
import org.springframework.boot.web.server.test.SpringBootTestWebEnvironmentContextHierarchyTests.ParentConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SpringBootTest @SpringBootTest} configured with
* {@link WebEnvironment#DEFINED_PORT}.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
@DirtiesContext
@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT, properties = { "server.port=0", "value=123" })
@ContextHierarchy({ @ContextConfiguration(classes = ParentConfiguration.class),
@ContextConfiguration(classes = ChildConfiguration.class) })
class SpringBootTestWebEnvironmentContextHierarchyTests {
@Autowired
private ApplicationContext context;
@Test
void testShouldOnlyStartSingleServer() {
ApplicationContext parent = this.context.getParent();
assertThat(this.context).isInstanceOf(WebApplicationContext.class);
assertThat(parent).isNotInstanceOf(WebApplicationContext.class);
}
@Configuration(proxyBeanMethods = false)
static class ParentConfiguration extends AbstractConfig {
}
@Configuration(proxyBeanMethods = false)
@EnableWebMvc
@RestController
static class ChildConfiguration extends AbstractConfig {
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.web.server.test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
* Tests for {@link SpringBootTest @SpringBootTest} configured with
* {@link WebEnvironment#DEFINED_PORT}.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
@DirtiesContext
@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT, properties = { "server.port=0", "value=123" })
class SpringBootTestWebEnvironmentDefinedPortTests extends AbstractSpringBootTestWebServerWebEnvironmentTests {
@Configuration(proxyBeanMethods = false)
@EnableWebMvc
@RestController
static class Config extends AbstractConfig {
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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.web.server.test;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.test.AbstractSpringBootTestWebServerWebEnvironmentTests.AbstractConfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link SpringBootTest @SpringBootTest} with a custom inline server.port in an
* embedded web environment.
*
* @author Stephane Nicoll
*/
@DirtiesContext
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { "server.port=12345" })
class SpringBootTestWebEnvironmentRandomPortCustomPortTests {
@Autowired
private Environment environment;
@Test
void validatePortIsNotOverwritten() {
String port = this.environment.getProperty("server.port");
assertThat(port).isEqualTo("0");
}
@Configuration(proxyBeanMethods = false)
@EnableWebMvc
static class Config extends AbstractConfig {
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.web.server.test;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SpringBootTest @SpringBootTest} configured with
* {@link WebEnvironment#RANDOM_PORT}.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
@DirtiesContext
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { "value=123" })
class SpringBootTestWebEnvironmentRandomPortTests extends AbstractSpringBootTestWebServerWebEnvironmentTests {
@Test
void testRestTemplateShouldUseBuilder() {
assertThat(getRestTemplate().getRestTemplate().getMessageConverters())
.hasAtLeastOneElementOfType(MyConverter.class);
}
@Configuration(proxyBeanMethods = false)
@EnableWebMvc
@RestController
static class Config extends AbstractConfig {
@Bean
RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder().additionalMessageConverters(new MyConverter());
}
}
static class MyConverter extends StringHttpMessageConverter {
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.web.server.test.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

@@ -0,0 +1,46 @@
/*
* 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.web.server.test.client;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import static org.assertj.core.api.Assertions.assertThat;
/**
* {@link ImportSelector} to check no {@link TestRestTemplate} definition is registered
* when config classes are processed.
*/
class NoTestRestTemplateBeanChecker implements ImportSelector, BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) {
assertThat(BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory) beanFactory,
TestRestTemplate.class))
.isEmpty();
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[0];
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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.web.server.test.client;
import java.io.IOException;
import java.io.PrintWriter;
import jakarta.servlet.GenericServlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link TestRestTemplateContextCustomizer}.
*
* @author Phillip Webb
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@DirtiesContext
class TestRestTemplateContextCustomizerIntegrationTests {
@Autowired
private TestRestTemplate restTemplate;
@Test
void test() {
assertThat(this.restTemplate.getForObject("/", String.class)).contains("hello");
}
@Configuration(proxyBeanMethods = false)
@Import({ TestServlet.class, NoTestRestTemplateBeanChecker.class })
static class TestConfig {
@Bean
TomcatServletWebServerFactory webServerFactory() {
return new TomcatServletWebServerFactory(0);
}
}
static class TestServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
try (PrintWriter writer = response.getWriter()) {
writer.println("hello");
}
}
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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.web.server.test.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.web.server.test.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

@@ -0,0 +1,62 @@
/*
* 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.web.server.test.client;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link TestRestTemplateContextCustomizer} to ensure
* early-initialization of factory beans doesn't occur.
*
* @author Madhura Bhave
*/
@SpringBootTest(classes = TestRestTemplateContextCustomizerWithFactoryBeanTests.TestClassWithFactoryBean.class,
webEnvironment = WebEnvironment.RANDOM_PORT)
@DirtiesContext
class TestRestTemplateContextCustomizerWithFactoryBeanTests {
@Autowired
private TestRestTemplate restTemplate;
@Test
void test() {
assertThat(this.restTemplate).isNotNull();
}
@Configuration(proxyBeanMethods = false)
@ComponentScan("org.springframework.boot.test.web.client.scan")
static class TestClassWithFactoryBean {
@Bean
TomcatServletWebServerFactory webServerFactory() {
return new TomcatServletWebServerFactory(0);
}
}
}

View File

@@ -0,0 +1,88 @@
/*
* 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.web.server.test.client;
import java.io.IOException;
import java.io.PrintWriter;
import jakarta.servlet.GenericServlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link TestRestTemplateContextCustomizer} with a custom
* {@link TestRestTemplate} bean.
*
* @author Phillip Webb
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@DirtiesContext
class TestRestTemplateContextCustomizerWithOverrideIntegrationTests {
@Autowired
private TestRestTemplate restTemplate;
@Test
void test() {
assertThat(this.restTemplate).isInstanceOf(CustomTestRestTemplate.class);
}
@Configuration(proxyBeanMethods = false)
@Import({ TestServlet.class, NoTestRestTemplateBeanChecker.class })
static class TestConfig {
@Bean
TomcatServletWebServerFactory webServerFactory() {
return new TomcatServletWebServerFactory(0);
}
@Bean
TestRestTemplate template() {
return new CustomTestRestTemplate();
}
}
static class TestServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
try (PrintWriter writer = response.getWriter()) {
writer.println("hello");
}
}
}
static class CustomTestRestTemplate extends TestRestTemplate {
}
}

View File

@@ -0,0 +1,505 @@
/*
* 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.web.server.test.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.web.client.RestTemplateBuilder;
import org.springframework.boot.web.server.test.client.TestRestTemplate.HttpClientOption;
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

@@ -0,0 +1,47 @@
/*
* 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.web.server.test.client.reactive;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.assertj.core.api.Assertions.assertThat;
/**
* {@link ImportSelector} to check no {@link WebTestClient} definition is registered when
* config classes are processed.
*/
class NoWebTestClientBeanChecker implements ImportSelector, BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) {
assertThat(BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory) beanFactory,
WebTestClient.class))
.isEmpty();
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[0];
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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.web.server.test.client.reactive;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.tomcat.reactive.TomcatReactiveWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.reactive.server.WebTestClient.Builder;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Integration test for {@link WebTestClientContextCustomizer}.
*
* @author Phillip Webb
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "spring.main.web-application-type=reactive")
@DirtiesContext
class WebTestClientContextCustomizerIntegrationTests {
@Autowired
private WebTestClient webTestClient;
@Autowired
private WebTestClientBuilderCustomizer clientBuilderCustomizer;
@Test
void test() {
then(this.clientBuilderCustomizer).should().customize(any(Builder.class));
this.webTestClient.get().uri("/").exchange().expectBody(String.class).isEqualTo("hello");
}
@Configuration(proxyBeanMethods = false)
@Import({ TestHandler.class, NoWebTestClientBeanChecker.class })
static class TestConfig {
@Bean
TomcatReactiveWebServerFactory webServerFactory() {
return new TomcatReactiveWebServerFactory(0);
}
@Bean
WebTestClientBuilderCustomizer clientBuilderCustomizer() {
return mock(WebTestClientBuilderCustomizer.class);
}
}
static class TestHandler implements HttpHandler {
private static final DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
response.setStatusCode(HttpStatus.OK);
return response.writeWith(Mono.just(factory.wrap("hello".getBytes())));
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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.web.server.test.client.reactive;
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.web.server.test.client.reactive.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

@@ -0,0 +1,87 @@
/*
* 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.web.server.test.client.reactive;
import java.util.Collections;
import java.util.Map;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.tomcat.reactive.TomcatReactiveWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ContextPathCompositeHandler;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* Tests for {@link WebTestClientContextCustomizer} with a custom base path for a reactive
* web application.
*
* @author Madhura Bhave
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = "spring.main.web-application-type=reactive")
@TestPropertySource(properties = "spring.webflux.base-path=/test")
class WebTestClientContextCustomizerWithCustomBasePathTests {
@Autowired
private WebTestClient webTestClient;
@Test
void test() {
this.webTestClient.get().uri("/hello").exchange().expectBody(String.class).isEqualTo("hello world");
}
@Configuration(proxyBeanMethods = false)
static class TestConfig {
@Bean
TomcatReactiveWebServerFactory webServerFactory() {
return new TomcatReactiveWebServerFactory(0);
}
@Bean
HttpHandler httpHandler() {
TestHandler httpHandler = new TestHandler();
Map<String, HttpHandler> handlersMap = Collections.singletonMap("/test", httpHandler);
return new ContextPathCompositeHandler(handlersMap);
}
}
static class TestHandler implements HttpHandler {
private static final DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
response.setStatusCode(HttpStatus.OK);
return response.writeWith(Mono.just(factory.wrap("hello world".getBytes())));
}
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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.web.server.test.client.reactive;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.DispatcherServlet;
/**
* Tests for {@link WebTestClientContextCustomizer} with a custom context path for a
* servlet web application.
*
* @author Madhura Bhave
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = "server.servlet.context-path=/test")
class WebTestClientContextCustomizerWithCustomContextPathTests {
@Autowired
private WebTestClient webTestClient;
@Test
void test() {
this.webTestClient.get().uri("/hello").exchange().expectBody(String.class).isEqualTo("hello world");
}
@Configuration(proxyBeanMethods = false)
@Import(TestController.class)
static class TestConfig {
@Bean
TomcatServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(0);
factory.setContextPath("/test");
return factory;
}
@Bean
DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
}
@RestController
static class TestController {
@GetMapping("/hello")
String hello() {
return "hello world";
}
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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.web.server.test.client.reactive;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.tomcat.reactive.TomcatReactiveWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Integration test for {@link WebTestClientContextCustomizer} with a custom
* {@link WebTestClient} bean.
*
* @author Phillip Webb
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "spring.main.web-application-type=reactive")
@DirtiesContext
class WebTestClientContextCustomizerWithOverrideIntegrationTests {
@Autowired
private WebTestClient webTestClient;
@Test
void test() {
assertThat(this.webTestClient).isInstanceOf(CustomWebTestClient.class);
}
@Configuration(proxyBeanMethods = false)
@Import({ TestHandler.class, NoWebTestClientBeanChecker.class })
static class TestConfig {
@Bean
TomcatReactiveWebServerFactory webServerFactory() {
return new TomcatReactiveWebServerFactory(0);
}
@Bean
WebTestClient webTestClient() {
return mock(CustomWebTestClient.class);
}
}
static class TestHandler implements HttpHandler {
private static final DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
response.setStatusCode(HttpStatus.OK);
return response.writeWith(Mono.just(factory.wrap("hello".getBytes())));
}
}
interface CustomWebTestClient extends WebTestClient {
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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.web.server.test.client.reactive;
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

@@ -0,0 +1,87 @@
/*
* 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.web.server.test.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

@@ -0,0 +1,144 @@
/*
* 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.web.server.test.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

@@ -0,0 +1,55 @@
/*
* 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.web.server.test.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

@@ -0,0 +1,265 @@
/*
* 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.web.server.test.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.web.server.test.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
}