Allow custom WebTestClient beans

Update `@SpringBootTest` `WebTestClient` support so that the bean
definition is only registered when the user has not defined or
auto-configured their own.

See gh-10556
pull/11775/head
Phillip Webb 7 years ago
parent 177281a504
commit b7c52da457

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -57,8 +57,9 @@ import org.springframework.web.context.WebApplicationContext;
* {@link WebEnvironment#DEFINED_PORT defined} or {@link WebEnvironment#RANDOM_PORT * {@link WebEnvironment#DEFINED_PORT defined} or {@link WebEnvironment#RANDOM_PORT
* random} port.</li> * random} port.</li>
* <li>Registers a {@link org.springframework.boot.test.web.client.TestRestTemplate * <li>Registers a {@link org.springframework.boot.test.web.client.TestRestTemplate
* TestRestTemplate} bean for use in web tests that are using a fully running web server. * TestRestTemplate} and/or
* </li> * {@link org.springframework.test.web.reactive.server.WebTestClient WebTestClient} bean
* for use in web tests that are using a fully running web server.</li>
* </ul> * </ul>
* *
* @author Phillip Webb * @author Phillip Webb

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,15 +14,21 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.test.web.reactive; package org.springframework.boot.test.web.reactive.server;
import java.util.Collection; import java.util.Collection;
import org.springframework.beans.BeansException; 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.FactoryBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; 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.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.codec.CodecCustomizer; import org.springframework.boot.web.codec.CodecCustomizer;
@ -30,6 +36,8 @@ import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFac
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.MergedContextConfiguration;
@ -63,8 +71,11 @@ class WebTestClientContextCustomizer implements ContextCustomizer {
private void registerWebTestClient(ConfigurableApplicationContext context, private void registerWebTestClient(ConfigurableApplicationContext context,
BeanDefinitionRegistry registry) { BeanDefinitionRegistry registry) {
registry.registerBeanDefinition(WebTestClient.class.getName(), RootBeanDefinition definition = new RootBeanDefinition(
new RootBeanDefinition(WebTestClientFactory.class)); WebTestClientRegistrar.class);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(WebTestClientRegistrar.class.getName(),
definition);
} }
@Override @Override
@ -77,6 +88,45 @@ class WebTestClientContextCustomizer implements ContextCustomizer {
return (obj != null && obj.getClass() == getClass()); return (obj != null && obj.getClass() == getClass());
} }
/**
* {@link BeanDefinitionRegistryPostProcessor} that runs after the
* {@link ConfigurationClassPostProcessor} and add a {@link WebTestClientFactory} bean
* definition when a {@link WebTestClient} hasn't already been registered.
*/
private 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 (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
(ListableBeanFactory) this.beanFactory,
WebTestClient.class).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}. * {@link FactoryBean} used to create and configure a {@link WebTestClient}.
*/ */

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.test.web.reactive; package org.springframework.boot.test.web.reactive.server;
import java.util.List; import java.util.List;
@ -30,7 +30,7 @@ import org.springframework.util.ClassUtils;
* *
* @author Stephane Nicoll * @author Stephane Nicoll
*/ */
public class WebTestClientContextCustomizerFactory implements ContextCustomizerFactory { class WebTestClientContextCustomizerFactory implements ContextCustomizerFactory {
private static final String WEB_TEST_CLIENT_CLASS = "org.springframework.web.reactive.function.client.WebClient"; private static final String WEB_TEST_CLIENT_CLASS = "org.springframework.web.reactive.function.client.WebClient";

@ -0,0 +1,21 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Spring Boot support for testing Spring WebFlux server endpoints via
* {@link org.springframework.test.web.reactive.server.WebTestClient}.
*/
package org.springframework.boot.test.web.reactive.server;

@ -5,7 +5,7 @@ org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizerFacto
org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory,\ org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory,\
org.springframework.boot.test.mock.mockito.MockitoContextCustomizerFactory,\ org.springframework.boot.test.mock.mockito.MockitoContextCustomizerFactory,\
org.springframework.boot.test.web.client.TestRestTemplateTestContextCustomizerFactory,\ org.springframework.boot.test.web.client.TestRestTemplateTestContextCustomizerFactory,\
org.springframework.boot.test.web.reactive.WebTestClientContextCustomizerFactory org.springframework.boot.test.web.reactive.server.WebTestClientContextCustomizerFactory
# Test Execution Listeners # Test Execution Listeners
org.springframework.test.context.TestExecutionListener=\ org.springframework.test.context.TestExecutionListener=\

@ -0,0 +1,47 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.test.web.reactive.server;
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.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) throws BeansException {
assertThat(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
(ListableBeanFactory) beanFactory, WebTestClient.class)).isEmpty();
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[0];
}
}

@ -0,0 +1,81 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.test.web.reactive.server;
import org.junit.Test;
import org.junit.runner.RunWith;
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.web.embedded.tomcat.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.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* Integration test for {@link WebTestClientContextCustomizer}.
*
* @author Phillip Webb
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "spring.main.web-application-type=reactive")
@DirtiesContext
public class WebTestClientContextCustomizerIntegrationTests {
@Autowired
private WebTestClient webTestClient;
@Test
public void test() {
this.webTestClient.get().uri("/").exchange().expectBody(String.class)
.isEqualTo("hello");
}
@Configuration
@Import({ TestHandler.class, NoWebTestClientBeanChecker.class })
static class TestConfig {
@Bean
public TomcatReactiveWebServerFactory webServerFactory() {
return new TomcatReactiveWebServerFactory(0);
}
}
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())));
}
}
}

@ -0,0 +1,93 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.test.web.reactive.server;
import org.junit.Test;
import org.junit.runner.RunWith;
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.web.embedded.tomcat.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.context.junit4.SpringRunner;
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
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "spring.main.web-application-type=reactive")
@DirtiesContext
public class WebTestClientContextCustomizerWithOverrideIntegrationTests {
@Autowired
private WebTestClient webTestClient;
@Test
public void test() {
assertThat(this.webTestClient).isInstanceOf(CustomWebTestClient.class);
}
@Configuration
@Import({ TestHandler.class, NoWebTestClientBeanChecker.class })
static class TestConfig {
@Bean
public TomcatReactiveWebServerFactory webServerFactory() {
return new TomcatReactiveWebServerFactory(0);
}
@Bean
public 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 {
}
}
Loading…
Cancel
Save