Create spring-boot-web-server-test module
This commit is contained in:
committed by
Phillip Webb
parent
96bee8e034
commit
4f579905ef
@@ -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>@Value("${local.management.port}")</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 {
|
||||
|
||||
}
|
||||
@@ -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>@Value("${local.server.port}")</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 {
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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:${local.server.port}</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;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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:${local.server.port}</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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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:${local.server.port}</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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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>() {})
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"groups": [],
|
||||
"properties": []
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user