From e274f29018065be4e6cfd42ee6d443629664bea8 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 12 Dec 2017 17:01:40 +0100 Subject: [PATCH] Add "testing your auto-configuration" doc section Closes gh-10011 --- .../src/main/asciidoc/index.adoc | 1 + .../main/asciidoc/spring-boot-features.adoc | 60 +++++++++++++ .../boot/autoconfigure/UserService.java | 36 ++++++++ .../UserServiceAutoConfiguration.java | 59 +++++++++++++ .../UserServiceAutoConfigurationTests.java | 85 +++++++++++++++++++ 5 files changed, 241 insertions(+) create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/autoconfigure/UserService.java create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/autoconfigure/UserServiceAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/autoconfigure/UserServiceAutoConfigurationTests.java diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/index.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/index.adoc index 35c176f2181..5f71dcb6dd3 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/index.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/index.adoc @@ -53,6 +53,7 @@ Phillip Webb; Dave Syer; Josh Long; Stéphane Nicoll; Rob Winch; Andy Wilkinson; :propdeps-plugin: https://github.com/spring-projects/gradle-plugins/tree/master/propdeps-plugin :ant-manual: http://ant.apache.org/manual :code-examples: ../java/org/springframework/boot +:test-examples: ../../test/java/org/springframework/boot :gradle-user-guide: https://docs.gradle.org/4.2.1/userguide :hibernate-documentation: http://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html :jetty-documentation: https://www.eclipse.org/jetty/documentation/9.4.x diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index e8d901659d6..fdc1e3aaaaa 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -7101,6 +7101,66 @@ result of a {spring-reference}core.html#expressions[SpEL expression]. +[[boot-features-test-autoconfig]] +=== Testing your auto-configuration +An auto-configuration can be affected by many factors: user configuration (`@Bean` +definition and `Environment` customization), condition evaluation (presence of a +particular library), etc. Concretely, each test should create a well-defined +`ApplicationContext` that represents a combination of those customizations and +`ApplicationContextRunner` provides a great way to achieve that. + +`ApplicationContextRunner` is usually defined as a field of the test class to gather the +base, common configuration. The following makes sure that `UserServiceAutoConfiguration` +is always invoked: + +[source,java,indent=0] +---- +include::{test-examples}/autoconfigure/UserServiceAutoConfigurationTests.java[tag=runner] +---- + +TIP: If multiple auto-configurations have to be defined, there is no need to order their +declarations as they will be invoked in the exact same order as when running the +application. + +Each test can use the runner to represents a particular use case. For instance, the sample +below invokes a user configuration (`UserConfiguration`) and checks that the +auto-configuration backs off properly. Invoking `run` provides a callback context that can +be used with `Assert4J`: + +[source,java,indent=0] +---- +include::{test-examples}/autoconfigure/UserServiceAutoConfigurationTests.java[tag=test-user-config] +---- + +It is also possible to easily customize the `Environment`: + +[source,java,indent=0] +---- +include::{test-examples}/autoconfigure/UserServiceAutoConfigurationTests.java[tag=test-env] +---- + + + +==== Simulating a web context +If you need to test an auto-configuration that only operates in a Servlet or Reactive web +application context, use the `WebApplicationContextRunner` or +`ReactiveWebApplicationContextRunner` respectively. + + + +==== Overriding the classpath +It is also possible to test what happens when a particular class and/or package is not +present at runtime. Spring Boot ships with a `FilteredClassLoader` that can easily be used +by the runner. In the example below, we assert that if `UserService` is not present, the +auto-configuration will be properly disabled: + +[source,java,indent=0] +---- +include::{test-examples}/autoconfigure/UserServiceAutoConfigurationTests.java[tag=test-classloader] +---- + + + [[boot-features-custom-starter]] === Creating Your Own Starter A full Spring Boot starter for a library may contain the following components: diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/autoconfigure/UserService.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/autoconfigure/UserService.java new file mode 100644 index 00000000000..de0259384da --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/autoconfigure/UserService.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2017 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.autoconfigure; + +/** + * Simple service. + * + * @author Stephane Nicoll + */ +class UserService { + + private final String name; + + UserService(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/autoconfigure/UserServiceAutoConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/autoconfigure/UserServiceAutoConfiguration.java new file mode 100644 index 00000000000..d6d24aa7022 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/autoconfigure/UserServiceAutoConfiguration.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2017 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.autoconfigure; + +import org.springframework.boot.autoconfigure.UserServiceAutoConfiguration.UserProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Sample auto-configuration. + * + * @author Stephane Nicoll + */ +@Configuration +@ConditionalOnClass(UserService.class) +@EnableConfigurationProperties(UserProperties.class) +public class UserServiceAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public UserService userService(UserProperties properties) { + return new UserService(properties.getName()); + } + + + @ConfigurationProperties("user") + static class UserProperties { + + private String name = "test"; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/autoconfigure/UserServiceAutoConfigurationTests.java b/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/autoconfigure/UserServiceAutoConfigurationTests.java new file mode 100644 index 00000000000..3b1278eb54b --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/autoconfigure/UserServiceAutoConfigurationTests.java @@ -0,0 +1,85 @@ +/* + * Copyright 2012-2017 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.autoconfigure; + +import org.junit.Test; + +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link UserServiceAutoConfiguration}. + * + * @author Stephane Nicoll + */ +public class UserServiceAutoConfigurationTests { + + // tag::runner[] + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(UserServiceAutoConfiguration.class)); + // end::runner[] + + // tag::test-env[] + @Test + public void serviceNameCanBeConfigured() { + this.contextRunner.withPropertyValues("user.name=test123") + .run((context) -> { + assertThat(context).hasSingleBean(UserService.class); + assertThat(context.getBean(UserService.class).getName()) + .isEqualTo("test123"); + }); + } + // end::test-env[] + + // tag::test-classloader[] + @Test + public void serviceIsIgnoredIfLibraryIsNotPresent() { + this.contextRunner.withClassLoader(new FilteredClassLoader(UserService.class)) + .run((context) -> { + assertThat(context).doesNotHaveBean("userService"); + }); + } + // end::test-classloader[] + + + // tag::test-user-config[] + @Test + public void defaultServiceBacksOff() { + this.contextRunner.withUserConfiguration(UserConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(UserService.class); + assertThat(context.getBean(UserService.class)).isSameAs( + context.getBean(UserConfiguration.class).myUserService()); + }); + } + + @Configuration + static class UserConfiguration { + + @Bean + public UserService myUserService() { + return new UserService("mine"); + } + + } + // end::test-user-config[] + +}